big view refactor into a new crate
[?]
May 14, 2025, 1:28 PM
23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQCDependencies
- [2]
6YZAVBWUInitial commit - [3]
IQDCHWCPload a pijul repo - [4]
SWWE2R6Mdisplay basic repo stuff - [5]
WT3GA27Padd cursor with selection - [6]
DVKSPF7Rtrack selected file path together with an index - [7]
UB2ITZJSrefresh changed files on FS changes - [8]
EC3TVL4Xadd untracked files - [9]
KT5UYXGKfix selection after adding file, add changed file diffs - [10]
ELG3UDT6allow to rm added files - [11]
S2NVIFXRallow to enter record msg - [12]
W7IUT3ZVstart recording impl - [13]
YBJRDOTCmake all repo actions async - [14]
KM5PSZ4Awatch repo once loaded - [15]
2VUX5BTDload identity - [16]
A5YBC77Vrecord! - [17]
D7A7MSIHallow to defer or abandon record, add buttons - [18]
UCBNZULEmake changed files paths optional (no path for root) - [19]
4WO3ZJM2show untracked files' contents - [20]
BJXUYQ2Yshow untracked file contents in read-only text editor - [21]
CFYW3HGZwip: display changed files - [22]
W4LFX7IHgroup diffs by file name - [23]
PTFDJ567add untracked files encoding - [24]
AMPZ2BXKshow changed files diffs (only Edit atm) - [25]
FDDPOH5Radd arrow controls - [26]
NOB64XMRfmt and clippy - [27]
AXSXZQDGfix updating changed file contents, styling - [28]
V55EAIWQadd src file LRU cache - [29]
Y5ATDI2Hconvert changed file diffs and load src only if any needs it - [30]
UMO6U2ZTpartition the change files diffs on whether they have content - [31]
6SW7UVSHupdate iced version - [32]
B4RMW5AEadd syntax highlighter to untracked files contents - [33]
MJDGPSHGWIP contents diff - [34]
ZVI4AWERwoot contents_diff - [35]
QMAUTRB6refactor diff - [36]
VUIRSTKHfmt - [37]
OQ6HSAWHshow record log - [38]
NWJD6VM6mv libflowers libflorescence - [39]
AHWWRC73navigate log entries - [40]
UJPRF6DAfix log changes selection - [41]
TEI5NQ3Sadd log files selection - [42]
DCSUCH6Radd undecoded diff view, improve decoded view style - [43]
O7PQIOJ3add missing undecoded diffs views - [44]
UUB7SHLRmore re-use in diffs - [45]
JE44NYHMdisplay log files diffs - [46]
4ELJZGRJload and store all change diffs at once - [47]
HC7ROIBCmove main diffs state out of cursor - [48]
FR52XEMWadd action for log change file diff - [49]
L6KSEFQImove cursor related stuff into its module - [50]
BFN2VHZSrefactor file stuff into sub-mod - [51]
VJNWIGSXclippy - [52]
GWZGYNIBadd view crate - [53]
3SYSJKYLadd app icon - [54]
CALXOZXAflatten crates dir - [55]
Z2CJPWZEfocus record message text_editor on spawn - [56]
WI2BVQ6Jrm client lib crate - [57]
HOJZI52Yrename flowers_ui to inflorescence - [58]
NRCUG4R2load changed files src when selected
Change contents
- file addition: util.rs[52.85]
use iced::Element;/// A short-named wrapper into `Element`#[inline(always)]pub fn el<'a, E, M>(e: E) -> Element<'a, M>whereE: Into<Element<'a, M>>,{Into::<Element<M>>::into(e)} - replacement in inflorescence_view/src/lib.rs at line 1
use iced::Element;pub mod app;pub mod diff;mod util; - replacement in inflorescence_view/src/lib.rs at line 5
/// A short-named wrapper into `Element`#[inline(always)]pub fn el<'a, E, M>(e: E) -> Element<'a, M>whereE: Into<Element<'a, M>>,{Into::<Element<M>>::into(e)use iced::{window, Theme};pub use util::el;pub fn theme<S>(_state: &S, _window_id: window::Id) -> Theme {Theme::TokyoNight - file addition: diff.rs[52.85]
use std::cmp;use iced::widget::{column, container, row, text};use iced::{alignment, Background, Color, Element, Font, Length};use crate::el;// TODO: maybe use themeconst DELETED_BG_COLOR: Color = Color::from_rgba8(190, 37, 40, 0.15);const ADDED_BG_COLOR: Color = Color::from_rgba8(47, 148, 11, 0.15);#[derive(Debug, Default)]pub struct State {pub selected_sections: Vec<usize>,pub expanded_unchanged_sections: Vec<usize>,pub collapsed_changed_sections: Vec<usize>,}#[derive(Debug, Clone)]pub enum Msg {}/// [`File`] is not part of [`State`] so it can be stored separately (i.e. in a/// cache, where it's immutable once set, unlike [`State`] which can change with/// [`Action`]s)#[derive(Debug)]pub enum File {Decoded(DecodedFile),Undecodable(UndecodableFile),}#[derive(Debug)]pub struct DecodedFile {pub combined: Combined,pub diffs_without_contents: Vec<DiffWithoutContents>,}#[derive(Debug)]pub struct UndecodableFile {pub diffs_with_contents: Vec<DiffWithContents>,pub diffs_without_contents: Vec<DiffWithoutContents>,}/// A file combined with its diffs into sections#[derive(Debug)]pub struct Combined {pub sections: Vec<Section>,pub max_line_num: usize,}#[derive(Debug)]pub enum DiffWithContents {Add,Edit {line: usize,deleted: bool,contents: String,},Replacement {line: usize,/// Deleted linechange_contents: String,/// Added linesreplacement_contents: String,},Del,Undel,}#[derive(Debug)]pub enum DiffWithoutContents {// _________________________________________________________________________// Cases that never have contents:Move,SolveNameConflict,UnsolveNameConflict,SolveOrderConflict,UnsolveOrderConflict,ResurrectZombines,AddRoot,DelRoot,// _________________________________________________________________________// Cases that normally have contents, but in these cases the contents are// not decodable:Edit {line: usize,deleted: bool,contents: UndecodableContents,},Replacement {line: usize,/// Deleted linechange_contents: UndecodableContents,/// Added linesreplacement_contents: UndecodableContents,},}#[derive(Debug)]pub enum UndecodableContents {/// Short byte sequence of unknown encoding encoded with base64 for/// display. Must be shorter than [`crate::repo::MAX_LEN_BASE64_DISPLAY`]ShortBase64(String),UnknownEncoding,}#[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,},}/// 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>;pub fn view<'a>(state: Option<&'a State>, file: &'a File) -> Element<'a, Msg> {match file {File::Decoded(decoded_file) => view_decoded(state, decoded_file),File::Undecodable(undecodable_file) => {view_undecodable(state, undecodable_file)}}}pub fn contents_to_lines(contents: &str) -> Lines {contents.split('\n').map(str::to_string).collect()}fn view_decoded<'a>(_state: Option<&'a State>,file: &'a DecodedFile,) -> Element<'a, Msg> {let DecodedFile {combined,diffs_without_contents,} = file;let line_num_digits = combined.max_line_num.to_string().len();// TODO use state to display selection, and control section expansionlet mut current_line = 1;let sections_view = combined.sections.iter().map(|section| match section {Section::Unchanged(lines) => {let res = lines.iter().enumerate().map(move |(ix, line)| {line_view(LineKind::Unchanged,current_line + ix,line_num_digits,line,)});current_line += lines.len();el(column(res))}Section::Changed { deleted, added } => {let res = deleted.iter().enumerate().map(move |(ix, line)| {line_view(LineKind::Deleted,current_line + ix,line_num_digits,line,)}).chain(added.iter().enumerate().map(move |(ix, line)| {line_view(LineKind::Added,current_line + ix,line_num_digits,line,)}));current_line += added.len();el(column(res))}});if diffs_without_contents.is_empty() {el(column(sections_view))} else {let diffs_without_contents_view = diffs_without_contents.iter().map(view_diff_without_contents);el(column([el(column(diffs_without_contents_view)),el(column(sections_view)),]).spacing(10))}}fn view_undecodable<'a>(_state: Option<&'a State>,file: &'a UndecodableFile,) -> Element<'a, Msg> {let UndecodableFile {diffs_with_contents,diffs_without_contents,} = file;let diffs = diffs_with_contents.iter().map(view_diff_with_contents).chain(diffs_without_contents.iter().map(view_diff_without_contents),);el(column(diffs).spacing(10))}/// View diffs without context (the file contents)fn view_diff_with_contents(diff: &DiffWithContents) -> Element<'_, Msg> {match diff {DiffWithContents::Add => el(text("Added")),DiffWithContents::Edit {line,deleted,contents,} => {let line_num = *line;let lines = contents_to_lines(contents);let max_line_num = line_num + lines.len();let line_num_digits = max_line_num.to_string().len();let lines_view = lines.into_iter().enumerate().map(|(ix, line)| {line_view(if *deleted {LineKind::Deleted} else {LineKind::Added},line_num + ix,line_num_digits,line,)});el(column(lines_view))}DiffWithContents::Replacement {line,change_contents,replacement_contents,} => {let line_num = *line;let change_lines = contents_to_lines(change_contents);let replacement_lines = contents_to_lines(replacement_contents);let max_line_num = line_num+ cmp::max(change_lines.len(), replacement_lines.len());let line_num_digits = max_line_num.to_string().len();let lines_view = change_lines.into_iter().enumerate().map(|(ix, line)| {line_view(LineKind::Deleted,line_num + ix,line_num_digits,line,)}).chain(replacement_lines.into_iter().enumerate().map(|(ix, line)| {line_view(LineKind::Added,line_num + ix,line_num_digits,line,)},));el(column(lines_view))}DiffWithContents::Del => el(text("Deleted")),DiffWithContents::Undel => el(text("Revived")),}}/// View diffs without context (the file contents)fn view_diff_without_contents(diff: &DiffWithoutContents) -> Element<'_, Msg> {match diff {DiffWithoutContents::Move => el(text("Move")),DiffWithoutContents::SolveNameConflict => {el(text("Solve name conflict"))}DiffWithoutContents::UnsolveNameConflict => {el(text("Unsolve name conflict"))}DiffWithoutContents::SolveOrderConflict => {el(text("Solve order conflict"))}DiffWithoutContents::UnsolveOrderConflict => {el(text("Unsolve order conflict"))}DiffWithoutContents::ResurrectZombines => el(text("Resurrect zombies")),DiffWithoutContents::AddRoot => el(text("Add root")),DiffWithoutContents::DelRoot => el(text("Delete root")),DiffWithoutContents::Edit {line,deleted,contents,} => {let line_num = *line;let line = undecodable_contents_to_str(contents);line_view(if *deleted {LineKind::Deleted} else {LineKind::Added},line_num,1,line,)}DiffWithoutContents::Replacement {line,change_contents,replacement_contents,} => {let line_num = *line;let change_line = undecodable_contents_to_str(change_contents);let replacement_line =undecodable_contents_to_str(replacement_contents);el(column([line_view(LineKind::Deleted, line_num, 1, change_line),line_view(LineKind::Added, line_num, 1, replacement_line),]))}}}fn mono_text<'a>(txt: impl text::IntoFragment<'a>) -> iced::widget::Text<'a> {text(txt).font(Font::MONOSPACE).wrapping(text::Wrapping::WordOrGlyph).align_y(alignment::Vertical::Top)}fn line_num_view<'a>(num: usize, digits: usize) -> iced::widget::Text<'a> {// Fill the string to the number of digitslet txt = format!("{num:digits$} ");mono_text(txt).font(Font::MONOSPACE).style(move |theme| {let palette = theme.extended_palette();text::Style {color: Some(palette.background.base.text.scale_alpha(0.61)),}}).align_y(alignment::Vertical::Top)}#[derive(Debug, Clone, Copy)]enum LineKind {Unchanged,Added,Deleted,}fn line_view<'a>(kind: LineKind,line_num: usize,line_num_digits: usize,line: impl text::IntoFragment<'a>,) -> Element<'a, Msg> {let line = container(row([el(mono_text(match kind {LineKind::Unchanged => " ",LineKind::Added => "+ ",LineKind::Deleted => "- ",})),el(line_num_view(line_num, line_num_digits)),el(mono_text(line).width(Length::Fill)),]));el(match kind {LineKind::Unchanged => line,LineKind::Added => line.style(|_theme| {container::background(Background::from(ADDED_BG_COLOR))}),LineKind::Deleted => line.style(|_theme| {container::background(Background::from(DELETED_BG_COLOR))}),})}fn undecodable_contents_to_str(contents: &UndecodableContents) -> &str {match contents {UndecodableContents::ShortBase64(short) => short,UndecodableContents::UnknownEncoding => "Unknown encoding",}} - file addition: app.rs[52.85]
//! Main app viewuse crate::{diff, el};use iced::widget::{button, column, row, scrollable, text};use iced::{font, window, Border, Color, Element, Font, Length, Theme};use libflorescence::prelude::*;use libflorescence::repo;use iced::widget::text_editor;use std::collections::HashMap;use std::path::Path;const SPACING: u32 = 10;#[derive(Debug)]pub struct State<'a> {pub repo_path: &'a Path,pub repo: Option<&'a repo::State>,pub cursor: &'a cursor::State,pub record_msg: Option<&'a RecordMsg>,pub diffs_state: &'a HashMap<file::Id, diff::State>,}#[derive(Debug, Clone)]pub enum Msg {Cursor(cursor::Msg),ToRepo(repo::MsgIn),EditRecordMsg(text_editor::Action),DeferRecord,SaveRecord,AbandonRecord,FileDiffsContentsAction {id: file::Id,action: diff::Msg,},LogChangeFileDiffAction {hash: pijul::Hash,file: String,action: diff::Msg,},}pub mod file {use crate::diff;#[derive(Debug, Clone, Hash, PartialEq, Eq)]pub struct Id {pub path: String,pub file_kind: Kind,}#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]pub enum Kind {Untracked,Changed,}#[derive(Debug)]pub enum Diff {Loading,Loaded(diff::File),}}pub mod cursor {use crate::diff;use libflorescence::prelude::pijul;use std::collections::HashMap;#[derive(Debug, Clone)]pub enum Msg {Down,Up,Right,Left,Select(Select),}#[derive(Debug, Default)]pub struct State {pub selection: Option<Selection>,}#[derive(Debug)]pub enum Selection {UntrackedFile {ix: usize,path: String,},ChangedFile {ix: usize,path: String,},LogChange {ix: usize,hash: pijul::Hash,message: String,/// All the diffs in this change keyed by file path. Loaded async/// and set to None only while loading. The/// `diff::State` is also in here so that is it/// preserved while navigating between files.diffs: Option<HashMap<String, (diff::File, diff::State)>>,file: Option<LogChangeFileSelection>,},}#[derive(Debug)]pub struct LogChangeFileSelection {pub ix: usize,pub path: String,}#[derive(Debug, Clone)]pub enum Select {UntrackedFile {ix: usize,path: String,},ChangedFile {ix: usize,path: String,},LogChange {ix: usize,hash: pijul::Hash,message: String,},LogChangeFile {ix: usize,path: String,},}}#[derive(Debug)]pub enum RecordMsg {Typing(text_editor::Content),Canceled { old_msg: String },}pub fn view<'a, F>(state: State<'a>,_window_id: window::Id,get_file_diff: F,) -> Element<'a, Msg>whereF: Fn(&file::Id) -> Option<&'a file::Diff>,{if let Some(repo) = state.repo.as_ref() {let repo_info = el(row([el(text(&repo.dir_name)),el(text(": ")),el(button(text(&repo.channel)), /* TODO* .on_press(Message) */),]));let untracked_files = || {el(column(repo.untracked_files.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(state.cursor.selection.as_ref() ,Some(cursor::Selection::UntrackedFile{ ix: selected_ix, .. }) if &ix == selected_ix);el(button(text(path)).on_press(Msg::Cursor(cursor::Msg::Select(cursor::Select::UntrackedFile{ix, path: path.clone()},))).style(selectable_button_style(is_selected)),)},)))};let changed_files = || {el(column(repo.changed_files.iter().enumerate().map(|(ix, (file_path, _diffs))| {let is_selected = matches!(state.cursor.selection.as_ref(),Some(cursor::Selection::ChangedFile{ ix: selected_ix, .. }) if &ix == selected_ix);el(button(text(file_path)).on_press(Msg::Cursor(cursor::Msg::Select(cursor::Select::ChangedFile{ix, path: file_path.clone()},))).style(selectable_button_style(is_selected)),)},)))};let log = || {el(column(repo.log.iter().enumerate().map(|(ix, repo::LogEntry {hash,message,file_paths: _,})| {let short_hash = display_short_hash(hash);let is_selected = matches!(state.cursor.selection.as_ref(),Some(cursor::Selection::LogChange { ix: selected_ix, .. }) if &ix == selected_ix);el(row([el(button(text(short_hash).font(Font::MONOSPACE)).on_press(Msg::Cursor(cursor::Msg::Select(cursor::Select::LogChange { ix, hash: *hash, message: message.clone() },))).style(selectable_button_style(is_selected))),el(text(message)),]).spacing(SPACING))},)))};let record_msg_editor = if let Some(RecordMsg::Typing(msg_content)) =state.record_msg.as_ref(){el(column([el(text_editor(msg_content).placeholder("Type something here...").on_action(Msg::EditRecordMsg)),el(row([el(button(text("Save")).on_press(Msg::SaveRecord)),el(button(text("Defer")).on_press(Msg::DeferRecord)),el(button(text("Abandon")).on_press(Msg::AbandonRecord)),])),]))} else {el(row([]))};let selection_details = match state.cursor.selection.as_ref() {Some(cursor::Selection::UntrackedFile { ix: _, path }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,};let diffs = match get_file_diff(&id) {Some(file::Diff::Loaded(file)) => {let selection_state = state.diffs_state.get(&id);diff::view(selection_state, file).map(move |msg| {Msg::FileDiffsContentsAction {id: id.clone(),action: msg,}})}None | Some(file::Diff::Loading) => {el(text("Loading diff..."))}};el(column([view_diff_header(format!("Untracked file {path} contents:")),el(scrollable(diffs)),]).spacing(SPACING))}Some(cursor::Selection::ChangedFile { path, ix: _ }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};let diffs = match get_file_diff(&id) {Some(file::Diff::Loaded(file)) => {let selection_state = state.diffs_state.get(&id);diff::view(selection_state, file).map(move |msg| {Msg::FileDiffsContentsAction {id: id.clone(),action: msg,}})}None | Some(file::Diff::Loading) => {el(text("Loading diff..."))}};el(column([view_diff_header(format!("Changed file {path} diff:")),el(scrollable(diffs)),]).spacing(SPACING))}Some(cursor::Selection::LogChange {ix,hash,message,diffs: _,file,}) => {let entry = state.repo.as_ref().unwrap().log.get(*ix).unwrap();let short_hash = display_short_hash(hash);let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(cursor::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Msg::Cursor(cursor::Msg::Select(cursor::Select::LogChangeFile { ix, path: path.clone() }))}).style(selectable_button_style(is_selected)))});el(column([view_diff_header(format!("{short_hash} message:")),el(text(message)),view_diff_header("Changed files:".to_string()),el(scrollable(column(files))),]).spacing(SPACING))}None => el(row([])),};let left_view = match state.cursor.selection.as_ref() {Some(cursor::Selection::LogChange {ix: _,hash,message: _,diffs,file: Some(cursor::LogChangeFileSelection { ix: _, path }),}) => el(column([view_diff_header(format!("{path} changes in {}:",display_short_hash(hash))),match diffs {Some(diffs) => {let (file, state) = diffs.get(path).unwrap();diff::view(Some(state), file).map(|action| {Msg::LogChangeFileDiffAction {hash: *hash,file: path.clone(),action,}})}None => el(text("Loading diff..")),},]).width(Length::FillPortion(1)).spacing(SPACING)),Some(cursor::Selection::UntrackedFile { .. })| Some(cursor::Selection::ChangedFile { .. })| Some(cursor::Selection::LogChange { .. })| None => el(column([repo_info,el(column([el(text("Untracked files:")), untracked_files()])),el(column([el(text("Changed files:")), changed_files()])),el(column([el(text("Recent changes:")), log()])),]).width(Length::FillPortion(1)).spacing(SPACING)),};let right_view = el(column([record_msg_editor, selection_details]).width(Length::FillPortion(1)));el(row([left_view, right_view]).spacing(SPACING))} else {el(text("Loading repo..."))}}fn view_diff_header(header: String) -> Element<'static, Msg> {el(text(header).font(Font {weight: font::Weight::Bold,..default()}))}fn selectable_button_style(is_selected: bool,) -> impl Fn(&Theme, button::Status) -> button::Style {move |theme, status| -> button::Style {button::Style {border: Border {color: if is_selected {Color::WHITE} else {Color::TRANSPARENT},width: 1.0,..default()},..button::Catalog::style(theme,&<Theme as button::Catalog>::default(),status,)}}}fn display_short_hash(hash: &pijul::Hash) -> String {let mut short_hash = pijul::Base32::to_base32(hash);short_hash.truncate(8);short_hash} - edit in inflorescence_view/Cargo.toml at line 16
workspace = true[dependencies.iced_test] - replacement in inflorescence/src/main.rs at line 11
use inflorescence_view::el;use inflorescence_view::app::RecordMsg;use inflorescence_view::{app, theme}; - replacement in inflorescence/src/main.rs at line 17[32.199]→[24.1316:1394](∅→∅),[19.1523]→[24.1316:1394](∅→∅),[24.1394]→[27.22:34](∅→∅),[27.34]→[53.21:108](∅→∅),[53.108]→[27.109:112](∅→∅),[34.150]→[27.109:112](∅→∅),[32.292]→[27.109:112](∅→∅),[27.109]→[27.109:112](∅→∅)
use iced::widget::{self, button, column, row, scrollable, text, text_editor};use iced::{font, window, Border, Color, Element, Font, Length, Subscription, Task,Theme,};use iced::widget::text_editor;use iced::{widget, window, Element, Subscription, Task}; - edit in inflorescence/src/main.rs at line 23
use pijul::Base32; - edit in inflorescence/src/main.rs at line 29
const SPACING: u32 = 10; - replacement in inflorescence/src/main.rs at line 51
fn init() -> (State, Task<Message>) {fn init() -> (State, Task<Msg>) { - replacement in inflorescence/src/main.rs at line 74
Message::RepoTaskExitedMsg::RepoTaskExited - replacement in inflorescence/src/main.rs at line 77
let repo_msg_out_task = Task::run(repo_rx_out, Message::FromRepo);let repo_msg_out_task = Task::run(repo_rx_out, Msg::FromRepo); - replacement in inflorescence/src/main.rs at line 81
Message::LoadedId(Box::new(id))Msg::LoadedId(Box::new(id)) - replacement in inflorescence/src/main.rs at line 87
open_window_task.map(Message::WindowOpened),open_window_task.map(Msg::WindowOpened), - replacement in inflorescence/src/main.rs at line 92
files_task.map(Message::File),files_task.map(Msg::File), - edit in inflorescence/src/main.rs at line 97
window_id, - edit in inflorescence/src/main.rs at line 113
window_id: window::Id, - edit in inflorescence/src/main.rs at line 124[28.1073]→[34.1324:1341](∅→∅),[23.525]→[17.78:163](∅→∅),[19.2723]→[17.78:163](∅→∅),[17.78]→[17.78:163](∅→∅),[2.3039]→[2.3039:3042](∅→∅)
#[derive(Debug)]enum RecordMsg {Typing(text_editor::Content),Canceled { old_msg: String },} - replacement in inflorescence/src/main.rs at line 125
enum Message {enum Msg {View(app::Msg), - edit in inflorescence/src/main.rs at line 131
ToRepo(repo::MsgIn),Cursor(cursor::Msg), - edit in inflorescence/src/main.rs at line 134
EditRecordMsg(text_editor::Action),DeferRecord,SaveRecord,AbandonRecord, - edit in inflorescence/src/main.rs at line 135[50.260]→[34.3034:3064](∅→∅),[28.2502]→[34.3034:3064](∅→∅),[34.3064]→[50.261:283](∅→∅),[50.283]→[35.331:361](∅→∅),[34.3084]→[35.331:361](∅→∅),[35.361]→[24.1914:1921](∅→∅),[34.3120]→[24.1914:1921](∅→∅),[24.1914]→[24.1914:1921](∅→∅),[24.1921]→[48.18:134](∅→∅)
FileDiffsContentsAction {id: file::Id,action: diff::Action,},LogChangeFileDiffAction {hash: pijul::Hash,file: String,action: diff::Action,}, - replacement in inflorescence/src/main.rs at line 137
fn update(state: &mut State, msg: Message) -> Task<Message> {fn update(state: &mut State, msg: Msg) -> Task<Msg> { - replacement in inflorescence/src/main.rs at line 139[13.6718]→[53.815:866](∅→∅),[53.866]→[17.217:252](∅→∅),[19.3253]→[17.217:252](∅→∅),[13.6718]→[17.217:252](∅→∅)
Message::WindowOpened(id) => Task::none(),Message::LoadedId(id) => {Msg::View(msg) => update_from_app_view(state, msg),Msg::WindowOpened(_id) => Task::none(),Msg::LoadedId(id) => { - replacement in inflorescence/src/main.rs at line 145
Message::RepoTaskExited => {Msg::RepoTaskExited => { - replacement in inflorescence/src/main.rs at line 149
Message::FromRepo(msg) => {Msg::FromRepo(msg) => { - edit in inflorescence/src/main.rs at line 152[13.6949]→[13.6949:6993](∅→∅),[13.6993]→[16.4597:4646](∅→∅),[16.4646]→[12.1273:1298](∅→∅),[13.7041]→[12.1273:1298](∅→∅),[8.3765]→[12.1273:1298](∅→∅)
}Message::ToRepo(msg) => {state.repo_tx_in.send(msg).unwrap();Task::none() - replacement in inflorescence/src/main.rs at line 153[7.1724]→[49.43:122](∅→∅),[49.122]→[50.284:314](∅→∅),[50.314]→[49.158:191](∅→∅),[49.158]→[49.158:191](∅→∅),[49.228]→[49.228:286](∅→∅),[49.286]→[9.6406:6445](∅→∅),[39.9672]→[9.6406:6445](∅→∅),[5.2657]→[9.6406:6445](∅→∅)
Message::Cursor(msg) => cursor::update(&mut state.cursor,&mut state.files,state.repo.as_ref(),msg,).map(Message::ToRepo),Message::AddUntrackedFile => {Msg::AddUntrackedFile => { - replacement in inflorescence/src/main.rs at line 191
Message::RmAddedFile => {Msg::RmAddedFile => { - replacement in inflorescence/src/main.rs at line 236
Message::Record => {Msg::Record => { - replacement in inflorescence/src/main.rs at line 264
Message::EditRecordMsg(action) => {Msg::File(msg) => {file::update(&mut state.files, state.repo.as_ref(), msg);Task::none()}}}fn update_from_app_view(state: &mut State, msg: app::Msg) -> Task<Msg> {match msg {app::Msg::Cursor(msg) => cursor::update(&mut state.cursor,&mut state.files,state.repo.as_ref(),msg,).map(|msg| Msg::View(app::Msg::ToRepo(msg))),app::Msg::ToRepo(msg) => {state.repo_tx_in.send(msg).unwrap();Task::none()}app::Msg::EditRecordMsg(action) => { - replacement in inflorescence/src/main.rs at line 292
Message::SaveRecord => {app::Msg::SaveRecord => { - replacement in inflorescence/src/main.rs at line 320
Message::DeferRecord => {app::Msg::DeferRecord => { - replacement in inflorescence/src/main.rs at line 333
Message::AbandonRecord => {app::Msg::AbandonRecord => { - replacement in inflorescence/src/main.rs at line 339
Message::FileDiffsContentsAction { id, action } => {app::Msg::FileDiffsContentsAction { id, action } => { - replacement in inflorescence/src/main.rs at line 363
Message::LogChangeFileDiffAction { hash, file, action } => {app::Msg::LogChangeFileDiffAction { hash, file, action } => { - edit in inflorescence/src/main.rs at line 383
Message::File(msg) => {file::update(&mut state.files, state.repo.as_ref(), msg);Task::none()} - replacement in inflorescence/src/main.rs at line 386
fn update_from_repo(state: &mut State, msg: repo::MsgOut) -> Task<Message> {fn update_from_repo(state: &mut State, msg: repo::MsgOut) -> Task<Msg> { - replacement in inflorescence/src/main.rs at line 419
Message::ToRepo(repo::MsgIn::RefreshChangedAndUntrackedFiles)Msg::View(app::Msg::ToRepo(repo::MsgIn::RefreshChangedAndUntrackedFiles,)) - replacement in inflorescence/src/main.rs at line 495
let task = Task::done(Message::ToRepo(let task = Task::done(Msg::View(app::Msg::ToRepo( - replacement in inflorescence/src/main.rs at line 497
));))); - replacement in inflorescence/src/main.rs at line 571
fn subs(_state: &State) -> Subscription<Message> {fn subs(_state: &State) -> Subscription<Msg> { - replacement in inflorescence/src/main.rs at line 578[5.2903]→[17.1991:2051](∅→∅),[17.2051]→[51.17:292](∅→∅),[51.292]→[17.2052:2102](∅→∅),[49.789]→[17.2052:2102](∅→∅),[41.5791]→[17.2052:2102](∅→∅),[5.3009]→[17.2052:2102](∅→∅),[17.2102]→[10.2052:2107](∅→∅),[9.7598]→[10.2052:2107](∅→∅)
"a" => Some(Message::AddUntrackedFile),"j" => Some(Message::Cursor(cursor::Msg::Down)),"k" => Some(Message::Cursor(cursor::Msg::Up)),"h" => Some(Message::Cursor(cursor::Msg::Left)),"l" => Some(Message::Cursor(cursor::Msg::Right)),"r" => Some(Message::Record),"x" => Some(Message::RmAddedFile),"a" => Some(Msg::AddUntrackedFile),"j" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::Down))),"k" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::Up))),"h" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::Left))),"l" => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::Right)))}"r" => Some(Msg::Record),"x" => Some(Msg::RmAddedFile), - replacement in inflorescence/src/main.rs at line 590
Some(Message::Cursor(cursor::Msg::Down))Some(Msg::View(app::Msg::Cursor(cursor::Msg::Down))) - replacement in inflorescence/src/main.rs at line 593
Some(Message::Cursor(cursor::Msg::Up))Some(Msg::View(app::Msg::Cursor(cursor::Msg::Up))) - replacement in inflorescence/src/main.rs at line 596
Some(Message::Cursor(cursor::Msg::Left))Some(Msg::View(app::Msg::Cursor(cursor::Msg::Left))) - replacement in inflorescence/src/main.rs at line 599
Some(Message::Cursor(cursor::Msg::Right))Some(Msg::View(app::Msg::Cursor(cursor::Msg::Right))) - replacement in inflorescence/src/main.rs at line 607
Some(Message::AbandonRecord)Some(Msg::View(app::Msg::AbandonRecord)) - replacement in inflorescence/src/main.rs at line 610
Some(Message::DeferRecord)Some(Msg::View(app::Msg::DeferRecord))}"s" if mods == Modifiers::CTRL => {Some(Msg::View(app::Msg::SaveRecord)) - edit in inflorescence/src/main.rs at line 615
"s" if mods == Modifiers::CTRL => Some(Message::SaveRecord), - edit in inflorescence/src/main.rs at line 621[5.3183]→[2.3221:3224](∅→∅),[4.5139]→[2.3221:3224](∅→∅),[19.8474]→[2.3221:3224](∅→∅),[2.3221]→[2.3221:3224](∅→∅),[2.3224]→[53.867:927](∅→∅),[53.927]→[45.16960:16982](∅→∅),[45.16960]→[45.16960:16982](∅→∅),[30.3294]→[29.6096:6099](∅→∅),[34.16535]→[29.6096:6099](∅→∅),[45.16982]→[29.6096:6099](∅→∅),[29.6096]→[29.6096:6099](∅→∅),[29.6099]→[53.928:997](∅→∅),[53.997]→[13.16477:16523](∅→∅),[3.1501]→[13.16477:16523](∅→∅),[13.16523]→[17.2445:2560](∅→∅),[17.2560]→[13.16682:16833](∅→∅),[13.16682]→[13.16682:16833](∅→∅),[13.16833]→[5.3530:3531](∅→∅),[4.5403]→[5.3530:3531](∅→∅),[5.3531]→[45.16983:17085](∅→∅),[17.2624]→[8.7755:7859](∅→∅),[13.16938]→[8.7755:7859](∅→∅),[45.17085]→[8.7755:7859](∅→∅),[8.7755]→[8.7755:7859](∅→∅),[8.7859]→[39.10971:11075](∅→∅),[39.11075]→[8.7965:7984](∅→∅),[34.16652]→[8.7965:7984](∅→∅),[8.7965]→[8.7965:7984](∅→∅),[8.7984]→[17.2625:2645](∅→∅),[17.2645]→[8.8015:8054](∅→∅),[8.8015]→[8.8015:8054](∅→∅),[8.8054]→[51.540:611](∅→∅),[51.611]→[34.16653:16736](∅→∅),[49.1353]→[34.16653:16736](∅→∅),[8.8111]→[34.16653:16736](∅→∅),[34.16736]→[49.1354:1382](∅→∅),[49.1382]→[8.8224:8327](∅→∅),[8.8224]→[8.8224:8327](∅→∅),[8.8327]→[45.17086:17109](∅→∅),[45.17109]→[8.8340:8341](∅→∅),[8.8340]→[8.8340:8341](∅→∅),[8.8341]→[45.17110:17208](∅→∅),[45.17208]→[22.4521:4563](∅→∅),[17.2707]→[22.4521:4563](∅→∅),[22.4563]→[39.11076:11254](∅→∅),[6.1118]→[5.3822:3841](∅→∅),[8.8446]→[5.3822:3841](∅→∅),[39.11254]→[5.3822:3841](∅→∅),[34.16850]→[5.3822:3841](∅→∅),[5.3822]→[5.3822:3841](∅→∅),[5.3841]→[17.2708:2728](∅→∅),[17.2728]→[18.1047:1075](∅→∅),[18.1075]→[26.298:331](∅→∅),[26.331]→[51.612:683](∅→∅),[51.683]→[34.16851:16937](∅→∅),[49.1460]→[34.16851:16937](∅→∅),[5.3968]→[34.16851:16937](∅→∅),[34.16937]→[49.1461:1489](∅→∅),[49.1489]→[5.4052:4155](∅→∅),[5.4052]→[5.4052:4155](∅→∅),[5.4155]→[45.17209:17232](∅→∅),[45.17232]→[5.4168:4169](∅→∅),[5.4168]→[5.4168:4169](∅→∅),[5.4169]→[45.17233:17311](∅→∅),[45.17311]→[39.11316:11451](∅→∅),[39.11316]→[39.11316:11451](∅→∅),[39.11451]→[45.17312:17371](∅→∅),[45.17371]→[39.11452:11648](∅→∅),[37.5173]→[39.11452:11648](∅→∅),[39.11648]→[37.5238:5333](∅→∅),[37.5238]→[37.5238:5333](∅→∅),[37.5333]→[51.684:755](∅→∅),[51.755]→[45.17372:17473](∅→∅),[49.1567]→[45.17372:17473](∅→∅),[37.5390]→[45.17372:17473](∅→∅),[45.17473]→[49.1568:1596](∅→∅),[49.1596]→[37.5485:5664](∅→∅),[37.5485]→[37.5485:5664](∅→∅),[37.5664]→[45.17474:17497](∅→∅),[17.2825]→[13.17203:17204](∅→∅),[37.5677]→[13.17203:17204](∅→∅),[45.17497]→[13.17203:17204](∅→∅),[13.17203]→[13.17203:17204](∅→∅),[13.17204]→[17.2826:3500](∅→∅),[17.3500]→[19.8475:8559](∅→∅),[19.8559]→[47.1782:1854](∅→∅),[47.1854]→[50.1239:1275](∅→∅),[50.1275]→[34.17129:17169](∅→∅),[34.17129]→[34.17129:17169](∅→∅),[34.17169]→[50.1276:1330](∅→∅),[50.1330]→[34.17221:17240](∅→∅),[34.17221]→[34.17221:17240](∅→∅),[34.17240]→[50.1331:1463](∅→∅),[50.1463]→[47.1855:1929](∅→∅),[34.17364]→[47.1855:1929](∅→∅),[47.1929]→[35.1245:1383](∅→∅),[34.17364]→[35.1245:1383](∅→∅),[35.1383]→[34.17510:17603](∅→∅),[34.17510]→[34.17510:17603](∅→∅),[34.17603]→[35.1384:1441](∅→∅),[35.1441]→[34.17660:17682](∅→∅),[34.17660]→[34.17660:17682](∅→∅),[34.17682]→[50.1464:1522](∅→∅),[50.1522]→[45.17554:17628](∅→∅),[45.17554]→[45.17554:17628](∅→∅),[28.6889]→[19.9266:9285](∅→∅),[45.17628]→[19.9266:9285](∅→∅),[34.17760]→[19.9266:9285](∅→∅),[19.9266]→[19.9266:9285](∅→∅),[19.9285]→[34.17761:17789](∅→∅),[34.17789]→[45.17629:17757](∅→∅),[45.17757]→[34.17852:17895](∅→∅),[34.17852]→[34.17852:17895](∅→∅),[34.17895]→[45.17758:17812](∅→∅),[27.380]→[19.9339:9353](∅→∅),[20.1352]→[19.9339:9353](∅→∅),[45.17812]→[19.9339:9353](∅→∅),[34.17915]→[19.9339:9353](∅→∅),[19.9339]→[19.9339:9353](∅→∅),[19.9353]→[47.1930:2000](∅→∅),[47.2000]→[50.1523:1559](∅→∅),[50.1559]→[34.18105:18145](∅→∅),[34.18105]→[34.18105:18145](∅→∅),[34.18145]→[50.1560:1612](∅→∅),[50.1612]→[34.18195:18214](∅→∅),[34.18195]→[34.18195:18214](∅→∅),[34.18214]→[50.1613:1745](∅→∅),[50.1745]→[47.2001:2075](∅→∅),[34.18338]→[47.2001:2075](∅→∅),[47.2075]→[35.1442:1580](∅→∅),[34.18338]→[35.1442:1580](∅→∅),[35.1580]→[34.18484:18577](∅→∅),[34.18484]→[34.18484:18577](∅→∅),[34.18577]→[35.1581:1638](∅→∅),[35.1638]→[33.4823:4845](∅→∅),[34.18604]→[33.4823:4845](∅→∅),[33.4823]→[33.4823:4845](∅→∅),[33.4845]→[50.1746:1804](∅→∅),[50.1804]→[45.17869:17943](∅→∅),[45.17869]→[45.17869:17943](∅→∅),[45.17943]→[34.18683:18730](∅→∅),[34.18683]→[34.18683:18730](∅→∅),[34.18730]→[45.17944:18020](∅→∅),[45.18020]→[34.18793:18836](∅→∅),[34.18793]→[34.18793:18836](∅→∅),[34.18836]→[45.18021:18075](∅→∅),[45.18075]→[37.5678:5692](∅→∅),[34.18856]→[37.5678:5692](∅→∅),[37.5692]→[45.18076:18191](∅→∅),[45.18191]→[46.11660:11686](∅→∅),[46.11686]→[45.18191:18313](∅→∅),[45.18191]→[45.18191:18313](∅→∅),[45.18313]→[39.11827:11828](∅→∅),[39.11827]→[39.11827:11828](∅→∅),[39.11828]→[45.18314:18373](∅→∅),[45.18373]→[39.11923:11924](∅→∅),[39.11923]→[39.11923:11924](∅→∅),[39.11924]→[41.5994:6077](∅→∅),[41.6077]→[45.18374:18518](∅→∅),[45.18518]→[41.6078:6144](∅→∅),[39.12134]→[41.6078:6144](∅→∅),[41.6144]→[51.756:875](∅→∅),[51.875]→[39.12270:12386](∅→∅),[49.1722]→[39.12270:12386](∅→∅),[41.6248]→[39.12270:12386](∅→∅),[39.12270]→[39.12270:12386](∅→∅),[39.12386]→[45.18519:18698](∅→∅),[45.18698]→[39.12466:12517](∅→∅),[39.12466]→[39.12466:12517](∅→∅),[39.12517]→[45.18699:18753](∅→∅),[21.515]→[19.9691:9738](∅→∅),[33.4970]→[19.9691:9738](∅→∅),[37.5774]→[19.9691:9738](∅→∅),[39.12537]→[19.9691:9738](∅→∅),[45.18753]→[19.9691:9738](∅→∅),[34.18856]→[19.9691:9738](∅→∅),[19.9691]→[19.9691:9738](∅→∅),[19.9738]→[17.3500:3511](∅→∅),[17.3500]→[17.3500:3511](∅→∅),[17.3511]→[5.4170:4171](∅→∅),[13.17593]→[5.4170:4171](∅→∅),[4.5731]→[5.4170:4171](∅→∅),[5.4171]→[45.18754:18939](∅→∅),[45.18939]→[46.11687:11786](∅→∅),[46.11786]→[45.19021:19203](∅→∅),[45.19021]→[45.19021:19203](∅→∅),[45.19203]→[46.11787:11924](∅→∅),[46.11924]→[48.885:1211](∅→∅),[48.1211]→[45.19347:19740](∅→∅),[47.2152]→[45.19347:19740](∅→∅),[45.19347]→[45.19347:19740](∅→∅),[45.19740]→[17.3553:3580](∅→∅),[17.3553]→[17.3553:3580](∅→∅),[17.3580]→[45.19741:19961](∅→∅),[37.5839]→[17.3863:3878](∅→∅),[45.19961]→[17.3863:3878](∅→∅),[17.3863]→[17.3863:3878](∅→∅),[17.3878]→[24.5482:5557](∅→∅),[24.5557]→[45.19962:20153](∅→∅),[24.5596]→[11.1940:1953](∅→∅),[13.18286]→[11.1940:1953](∅→∅),[45.20153]→[11.1940:1953](∅→∅),[11.1940]→[11.1940:1953](∅→∅),[11.1953]→[17.3999:4035](∅→∅),[17.4035]→[13.18334:18340](∅→∅),[13.18334]→[13.18334:18340](∅→∅),[13.18340]→[27.445:611](∅→∅)
}fn theme(_state: &State, _window_id: window::Id) -> Theme {Theme::TokyoNight}fn view(state: &State, _window_id: window::Id) -> Element<Message> {if let Some(repo) = state.repo.as_ref() {let repo_info = el(row([el(text(&repo.dir_name)),el(text(": ")),el(button(text(&repo.channel)), /* TODO* .on_press(Message) */),]));let untracked_files = || {el(column(repo.untracked_files.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(state.cursor.selection.as_ref() ,Some(cursor::Selection::UntrackedFile{ ix: selected_ix, .. }) if &ix == selected_ix);el(button(text(path)).on_press(Message::Cursor(cursor::Msg::Select(cursor::Select::UntrackedFile{ix, path: path.clone()},))).style(selectable_button_style(is_selected)),)},)))};let changed_files = || {el(column(repo.changed_files.iter().enumerate().map(|(ix, (file_path, _diffs))| {let is_selected = matches!(state.cursor.selection.as_ref(),Some(cursor::Selection::ChangedFile{ ix: selected_ix, .. }) if &ix == selected_ix);el(button(text(file_path)).on_press(Message::Cursor(cursor::Msg::Select(cursor::Select::ChangedFile{ix, path: file_path.clone()},))).style(selectable_button_style(is_selected)),)},)))};let log = || {el(column(repo.log.iter().enumerate().map(|(ix, repo::LogEntry {hash,message,file_paths: _,})| {let short_hash = display_short_hash(hash);let is_selected = matches!(state.cursor.selection.as_ref(),Some(cursor::Selection::LogChange { ix: selected_ix, .. }) if &ix == selected_ix);el(row([el(button(text(short_hash).font(Font::MONOSPACE)).on_press(Message::Cursor(cursor::Msg::Select(cursor::Select::LogChange { ix, hash: *hash, message: message.clone() },))).style(selectable_button_style(is_selected))),el(text(message)),]).spacing(SPACING))},)))};let record_msg_editor = if let Some(RecordMsg::Typing(msg_content)) =state.record_msg.as_ref(){el(column([el(text_editor(msg_content).placeholder("Type something here...").on_action(Message::EditRecordMsg)),el(row([el(button(text("Save")).on_press(Message::SaveRecord)),el(button(text("Defer")).on_press(Message::DeferRecord)),el(button(text("Abandon")).on_press(Message::AbandonRecord)),])),]))} else {el(row([]))};let selection_details = match state.cursor.selection.as_ref() {Some(cursor::Selection::UntrackedFile { ix: _, path }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,};let diffs = match state.files.diffs_cache.inner.peek(&id) {Some(file::Diff::Loaded(file)) => {let selection_state = state.diffs_state.get(&id);diff::view(selection_state, file).map(move |msg| {Message::FileDiffsContentsAction {id: id.clone(),action: msg,}})}None | Some(file::Diff::Loading) => {el(text("Loading diff..."))}};el(column([view_diff_header(format!("Untracked file {path} contents:")),el(scrollable(diffs)),]).spacing(SPACING))}Some(cursor::Selection::ChangedFile { path, ix: _ }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};let diffs = match state.files.diffs_cache.inner.peek(&id) {Some(file::Diff::Loaded(file)) => {let selection_state = state.diffs_state.get(&id);diff::view(selection_state, file).map(move |msg| {Message::FileDiffsContentsAction {id: id.clone(),action: msg,}})}None | Some(file::Diff::Loading) => {el(text("Loading diff..."))}};el(column([view_diff_header(format!("Changed file {path} diff:")),el(scrollable(diffs)),]).spacing(SPACING))}Some(cursor::Selection::LogChange {ix,hash,message,diffs: _,file,}) => {let entry = state.repo.as_ref().unwrap().log.get(*ix).unwrap();let short_hash = display_short_hash(hash);let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(cursor::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Message::Cursor(cursor::Msg::Select(cursor::Select::LogChangeFile { ix, path: path.clone() }))}).style(selectable_button_style(is_selected)))});el(column([view_diff_header(format!("{short_hash} message:")),el(text(message)),view_diff_header("Changed files:".to_string()),el(scrollable(column(files))),]).spacing(SPACING))}None => el(row([])),};let left_view = match state.cursor.selection.as_ref() {Some(cursor::Selection::LogChange {ix: _,hash,message: _,diffs,file: Some(cursor::LogChangeFileSelection { ix: _, path }),}) => el(column([view_diff_header(format!("{path} changes in {}:",display_short_hash(hash))),match diffs {Some(diffs) => {let (file, state) = diffs.get(path).unwrap();diff::view(Some(state), file).map(|action| {Message::LogChangeFileDiffAction {hash: *hash,file: path.clone(),action,}})}None => el(text("Loading diff..")),},]).width(Length::FillPortion(1)).spacing(SPACING)),Some(cursor::Selection::UntrackedFile { .. })| Some(cursor::Selection::ChangedFile { .. })| Some(cursor::Selection::LogChange { .. })| None => el(column([repo_info,el(column([el(text("Untracked files:")), untracked_files()])),el(column([el(text("Changed files:")), changed_files()])),el(column([el(text("Recent changes:")), log()])),]).width(Length::FillPortion(1)).spacing(SPACING)),};let right_view = el(column([record_msg_editor, selection_details]).width(Length::FillPortion(1)));el(row([left_view, right_view]).spacing(SPACING))} else {el(text("Loading repo..."))}}fn view_diff_header(header: String) -> Element<'static, Message> {el(text(header).font(Font {weight: font::Weight::Bold,..default()})) - replacement in inflorescence/src/main.rs at line 623
fn selectable_button_style(is_selected: bool,) -> impl Fn(&Theme, button::Status) -> button::Style {move |theme, status| -> button::Style {button::Style {border: Border {color: if is_selected {Color::WHITE} else {Color::TRANSPARENT},width: 1.0,..default()},..button::Catalog::style(theme,&<Theme as button::Catalog>::default(),status,)}}fn view(state: &State, window_id: window::Id) -> Element<Msg> {let State {id: _,repo_fs_watch: _,repo_path,repo_tx_in: _,repo,cursor,record_msg,files,diffs_state,} = state;app::view(app::State {repo_path,repo: repo.as_ref(),cursor,record_msg: record_msg.as_ref(),diffs_state,},window_id,|id| files.diffs_cache.inner.peek(id),).map(Msg::View) - edit in inflorescence/src/main.rs at line 648[45.20156]→[45.20156:20297](∅→∅),[3.1579]→[2.3333:3335](∅→∅),[17.4129]→[2.3333:3335](∅→∅),[5.4777]→[2.3333:3335](∅→∅),[4.6123]→[2.3333:3335](∅→∅),[45.20297]→[2.3333:3335](∅→∅),[2.3333]→[2.3333:3335](∅→∅)
fn display_short_hash(hash: &pijul::Hash) -> String {let mut short_hash = hash.to_base32();short_hash.truncate(8);short_hash} - edit in inflorescence/src/file.rs at line 2
pub use inflorescence_view::app::file::{Diff, Id, Kind}; - edit in inflorescence/src/file.rs at line 49
#[derive(Debug)]pub enum Diff {Loading,Loaded(diff::File),}#[derive(Debug, Clone, Hash, PartialEq, Eq)]pub struct Id {pub path: String,pub file_kind: Kind,}#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]pub enum Kind {Untracked,Changed,} - replacement in inflorescence/src/diff.rs at line 3
use std::borrow::Cow;use std::cmp;pub use inflorescence_view::diff::*; - edit in inflorescence/src/diff.rs at line 5[52.732]→[52.732:760](∅→∅),[52.760]→[33.5070:5071](∅→∅),[33.5070]→[33.5070:5071](∅→∅),[33.5071]→[42.19:69](∅→∅),[42.69]→[45.20299:20364](∅→∅)
use inflorescence_view::el;use iced::widget::{column, container, row, text};use iced::{alignment, Background, Color, Element, Font, Length}; - replacement in inflorescence/src/diff.rs at line 7
// TODO: maybe use themeconst DELETED_BG_COLOR: Color = Color::from_rgba8(190, 37, 40, 0.15);const ADDED_BG_COLOR: Color = Color::from_rgba8(47, 148, 11, 0.15);use std::borrow::Cow;use std::cmp; - replacement in inflorescence/src/diff.rs at line 10
#[derive(Debug, Default)]pub struct State {pub selected_sections: Vec<usize>,pub expanded_unchanged_sections: Vec<usize>,pub collapsed_changed_sections: Vec<usize>,}pub fn update(_state: &mut State, _msg: Msg) {} - edit in inflorescence/src/diff.rs at line 12[35.1928]→[35.1928:2027](∅→∅),[35.2027]→[47.2154:2248](∅→∅),[47.2248]→[35.2102:2395](∅→∅),[35.2102]→[35.2102:2395](∅→∅),[35.2395]→[36.23:121](∅→∅),[36.121]→[33.5154:5171](∅→∅),[35.2489]→[33.5154:5171](∅→∅),[33.5154]→[33.5154:5171](∅→∅),[33.5171]→[34.19212:19291](∅→∅)
#[derive(Debug, Clone)]pub enum Action {}pub fn update(_state: &mut State, _action: Action) {}pub fn view<'a>(state: Option<&'a State>,file: &'a File,) -> Element<'a, Action> {match file {File::Decoded(decoded_file) => view_decoded(state, decoded_file),File::Undecodable(undecodable_file) => {view_undecodable(state, undecodable_file)}}}/// [`File`] is not part of [`State`] so it can be stored separately (i.e. in a/// cache, where it's immutable once set, unlike [`State`] which can change with/// [`Action`]s)#[derive(Debug)]pub enum File {Decoded(DecodedFile),Undecodable(UndecodableFile),} - edit in inflorescence/src/diff.rs at line 13[34.19308]→[34.19308:19419](∅→∅),[34.19419]→[33.5517:5537](∅→∅),[33.5517]→[33.5517:5537](∅→∅),[33.5537]→[34.19420:19559](∅→∅),[34.19559]→[33.5767:5770](∅→∅),[33.5767]→[33.5767:5770](∅→∅),[33.5770]→[34.19560:19609](∅→∅),[34.19609]→[33.5770:5787](∅→∅),[33.5770]→[33.5770:5787](∅→∅),[33.5787]→[34.19610:19632](∅→∅),[34.19632]→[33.5806:5887](∅→∅),[33.5806]→[33.5806:5887](∅→∅)
pub struct DecodedFile {pub combined: Combined,pub diffs_without_contents: Vec<DiffWithoutContents>,}#[derive(Debug)]pub struct UndecodableFile {pub diffs_with_contents: Vec<DiffWithContents>,pub diffs_without_contents: Vec<DiffWithoutContents>,}/// A file combined with its diffs into sections#[derive(Debug)]pub struct Combined {pub sections: Vec<Section>,pub max_line_num: usize,}#[derive(Debug)] - edit in inflorescence/src/diff.rs at line 16
}#[derive(Debug)]pub enum DiffWithContents {Add,Edit {line: usize,deleted: bool,contents: String,},Replacement {line: usize,/// Deleted linechange_contents: String,/// Added linesreplacement_contents: String,},Del,Undel, - edit in inflorescence/src/diff.rs at line 18[35.2915]→[35.2915:3717](∅→∅),[35.3787]→[35.3787:4010](∅→∅),[35.4010]→[34.19687:19708](∅→∅),[34.19687]→[34.19687:19708](∅→∅),[34.19708]→[35.4011:4288](∅→∅),[35.4288]→[34.19708:19710](∅→∅),[34.19708]→[34.19708:19710](∅→∅),[34.19710]→[35.4289:4431](∅→∅),[35.4431]→[34.19710:19711](∅→∅),[34.19710]→[34.19710:19711](∅→∅)
#[derive(Debug)]pub enum DiffWithoutContents {// _________________________________________________________________________// Cases that never have contents:Move,SolveNameConflict,UnsolveNameConflict,SolveOrderConflict,UnsolveOrderConflict,ResurrectZombines,AddRoot,DelRoot,// _________________________________________________________________________// Cases that normally have contents, but in these cases the contents are// not decodable:Edit {line: usize,deleted: bool,contents: UndecodableContents,},Replacement {line: usize,/// Deleted linechange_contents: UndecodableContents,/// Added linesreplacement_contents: UndecodableContents,},}#[derive(Debug)]pub enum UndecodableContents {/// Short byte sequence of unknown encoding encoded with base64 for/// display. Must be shorter than [`crate::repo::MAX_LEN_BASE64_DISPLAY`]ShortBase64(String),UnknownEncoding,}#[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,},}/// 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>; - edit in inflorescence/src/diff.rs at line 247[33.9933]→[33.9933:9939](∅→∅),[34.25625]→[34.25625:25628](∅→∅),[34.27679]→[34.27679:27782](∅→∅),[34.27782]→[43.19:242](∅→∅)
}}fn contents_to_lines(contents: &str) -> Lines {contents.split('\n').map(str::to_string).collect()}fn undecodable_contents_to_str(contents: &UndecodableContents) -> &str {match contents {UndecodableContents::ShortBase64(short) => short,UndecodableContents::UnknownEncoding => "Unknown encoding", - edit in inflorescence/src/diff.rs at line 263[34.28144]→[33.10832:10833](∅→∅),[33.10832]→[33.10832:10833](∅→∅),[33.10833]→[35.8703:8724](∅→∅),[35.8724]→[47.2249:2280](∅→∅),[47.2280]→[34.28193:28220](∅→∅),[34.28193]→[34.28193:28220](∅→∅),[34.28220]→[35.8725:8752](∅→∅),[35.8752]→[42.289:444](∅→∅),[42.444]→[34.28244:28318](∅→∅),[35.8752]→[34.28244:28318](∅→∅),[34.28244]→[34.28244:28318](∅→∅),[34.28318]→[33.10886:10916](∅→∅),[33.10886]→[33.10886:10916](∅→∅),[33.10916]→[44.163:556](∅→∅),[44.556]→[33.11717:11718](∅→∅),[33.11717]→[33.11717:11718](∅→∅),[33.11718]→[44.557:1339](∅→∅),[44.1339]→[33.12921:12922](∅→∅),[33.12921]→[33.12921:12922](∅→∅),[33.12922]→[44.1340:1427](∅→∅),[44.1427]→[42.1849:2223](∅→∅),[33.13042]→[42.1849:2223](∅→∅),[42.2223]→[34.28439:28442](∅→∅),[33.13072]→[34.28439:28442](∅→∅),[34.28442]→[35.8753:8778](∅→∅),[35.8778]→[47.2281:2312](∅→∅),[47.2312]→[34.28494:28525](∅→∅),[34.28494]→[34.28494:28525](∅→∅),[34.28525]→[35.8779:8806](∅→∅),[35.8806]→[42.2224:2553](∅→∅),[42.2553]→[45.20365:20399](∅→∅),[45.20399]→[42.2575:3093](∅→∅),[42.2575]→[42.2575:3093](∅→∅),[42.3093]→[44.1428:1817](∅→∅),[44.1817]→[42.3422:3977](∅→∅),[42.3422]→[42.3422:3977](∅→∅),[42.4025]→[42.4025:4067](∅→∅),[42.4067]→[44.1818:1847](∅→∅),[44.1847]→[42.4091:4156](∅→∅),[42.4091]→[42.4091:4156](∅→∅),[42.4156]→[44.1848:2054](∅→∅),[44.2054]→[42.4611:4630](∅→∅),[42.4611]→[42.4611:4630](∅→∅),[42.4630]→[44.2055:2125](∅→∅),[44.2125]→[42.4695:4730](∅→∅),[42.4695]→[42.4695:4730](∅→∅),[42.4730]→[44.2126:2354](∅→∅),[44.2354]→[42.5223:5427](∅→∅),[42.5223]→[42.5223:5427](∅→∅),[42.5427]→[33.13072:13075](∅→∅),[34.28618]→[33.13072:13075](∅→∅),[33.13072]→[33.13072:13075](∅→∅),[33.13075]→[42.5428:5586](∅→∅),[42.5586]→[43.249:948](∅→∅),[43.948]→[42.6031:6128](∅→∅),[42.6031]→[42.6031:6128](∅→∅),[42.6128]→[43.949:1061](∅→∅),[43.1061]→[44.2355:2607](∅→∅),[44.2607]→[43.1270:1280](∅→∅),[43.1270]→[43.1270:1280](∅→∅),[43.1280]→[42.6150:6274](∅→∅),[42.6150]→[42.6150:6274](∅→∅),[42.6274]→[43.1281:1533](∅→∅),[43.1533]→[44.2608:2755](∅→∅),[44.2755]→[43.1952:1978](∅→∅),[43.1952]→[43.1952:1978](∅→∅),[43.1978]→[42.6296:6305](∅→∅),[42.6296]→[42.6296:6305](∅→∅),[42.6305]→[44.2756:2835](∅→∅),[44.2835]→[45.20400:20535](∅→∅),[45.20535]→[33.13185:13187](∅→∅),[33.13185]→[33.13185:13187](∅→∅),[33.13187]→[42.6385:6509](∅→∅),[42.6509]→[45.20536:20880](∅→∅),[45.20880]→[42.6789:6791](∅→∅),[42.6789]→[42.6789:6791](∅→∅),[42.6791]→[44.2899:3777](∅→∅)
fn view_decoded<'a>(_state: Option<&'a State>,file: &'a DecodedFile,) -> Element<'a, Action> {let DecodedFile {combined,diffs_without_contents,} = file;let line_num_digits = combined.max_line_num.to_string().len();// TODO use state to display selection, and control section expansionlet mut current_line = 1;let sections_view = combined.sections.iter().map(|section| match section {Section::Unchanged(lines) => {let res = lines.iter().enumerate().map(move |(ix, line)| {line_view(LineKind::Unchanged,current_line + ix,line_num_digits,line,)});current_line += lines.len();el(column(res))}Section::Changed { deleted, added } => {let res = deleted.iter().enumerate().map(move |(ix, line)| {line_view(LineKind::Deleted,current_line + ix,line_num_digits,line,)}).chain(added.iter().enumerate().map(move |(ix, line)| {line_view(LineKind::Added,current_line + ix,line_num_digits,line,)}));current_line += added.len();el(column(res))}});if diffs_without_contents.is_empty() {el(column(sections_view))} else {let diffs_without_contents_view = diffs_without_contents.iter().map(view_diff_without_contents);el(column([el(column(diffs_without_contents_view)),el(column(sections_view)),]).spacing(10))}}fn view_undecodable<'a>(_state: Option<&'a State>,file: &'a UndecodableFile,) -> Element<'a, Action> {let UndecodableFile {diffs_with_contents,diffs_without_contents,} = file;let diffs = diffs_with_contents.iter().map(view_diff_with_contents).chain(diffs_without_contents.iter().map(view_diff_without_contents),);el(column(diffs).spacing(10))}/// View diffs without context (the file contents)fn view_diff_with_contents(diff: &DiffWithContents) -> Element<'_, Action> {match diff {DiffWithContents::Add => el(text("Added")),DiffWithContents::Edit {line,deleted,contents,} => {let line_num = *line;let lines = contents_to_lines(contents);let max_line_num = line_num + lines.len();let line_num_digits = max_line_num.to_string().len();let lines_view = lines.into_iter().enumerate().map(|(ix, line)| {line_view(if *deleted {LineKind::Deleted} else {LineKind::Added},line_num + ix,line_num_digits,line,)});el(column(lines_view))}DiffWithContents::Replacement {line,change_contents,replacement_contents,} => {let line_num = *line;let change_lines = contents_to_lines(change_contents);let replacement_lines = contents_to_lines(replacement_contents);let max_line_num = line_num+ cmp::max(change_lines.len(), replacement_lines.len());let line_num_digits = max_line_num.to_string().len();let lines_view = change_lines.into_iter().enumerate().map(|(ix, line)| {line_view(LineKind::Deleted,line_num + ix,line_num_digits,line,)}).chain(replacement_lines.into_iter().enumerate().map(|(ix, line)| {line_view(LineKind::Added,line_num + ix,line_num_digits,line,)},));el(column(lines_view))}DiffWithContents::Del => el(text("Deleted")),DiffWithContents::Undel => el(text("Revived")),}}/// View diffs without context (the file contents)fn view_diff_without_contents(diff: &DiffWithoutContents,) -> Element<'_, Action> {match diff {DiffWithoutContents::Move => el(text("Move")),DiffWithoutContents::SolveNameConflict => {el(text("Solve name conflict"))}DiffWithoutContents::UnsolveNameConflict => {el(text("Unsolve name conflict"))}DiffWithoutContents::SolveOrderConflict => {el(text("Solve order conflict"))}DiffWithoutContents::UnsolveOrderConflict => {el(text("Unsolve order conflict"))}DiffWithoutContents::ResurrectZombines => el(text("Resurrect zombies")),DiffWithoutContents::AddRoot => el(text("Add root")),DiffWithoutContents::DelRoot => el(text("Delete root")),DiffWithoutContents::Edit {line,deleted,contents,} => {let line_num = *line;let line = undecodable_contents_to_str(contents);line_view(if *deleted {LineKind::Deleted} else {LineKind::Added},line_num,1,line,)}DiffWithoutContents::Replacement {line,change_contents,replacement_contents,} => {let line_num = *line;let change_line = undecodable_contents_to_str(change_contents);let replacement_line =undecodable_contents_to_str(replacement_contents);el(column([line_view(LineKind::Deleted, line_num, 1, change_line),line_view(LineKind::Added, line_num, 1, replacement_line),]))}}}fn mono_text<'a>(txt: impl text::IntoFragment<'a>) -> iced::widget::Text<'a> {text(txt).font(Font::MONOSPACE).wrapping(text::Wrapping::WordOrGlyph).align_y(alignment::Vertical::Top)}fn line_num_view<'a>(num: usize, digits: usize) -> iced::widget::Text<'a> {// Fill the string to the number of digitslet txt = format!("{num:digits$} ");mono_text(txt).font(Font::MONOSPACE).style(move |theme| {let palette = theme.extended_palette();text::Style {color: Some(palette.background.base.text.scale_alpha(0.61)),}}).align_y(alignment::Vertical::Top)}#[derive(Debug, Clone, Copy)]enum LineKind {Unchanged,Added,Deleted,}fn line_view<'a>(kind: LineKind,line_num: usize,line_num_digits: usize,line: impl text::IntoFragment<'a>,) -> Element<'a, Action> {let line = container(row([el(mono_text(match kind {LineKind::Unchanged => " ",LineKind::Added => "+ ",LineKind::Deleted => "- ",})),el(line_num_view(line_num, line_num_digits)),el(mono_text(line).width(Length::Fill)),]));el(match kind {LineKind::Unchanged => line,LineKind::Added => line.style(|_theme| {container::background(Background::from(ADDED_BG_COLOR))}),LineKind::Deleted => line.style(|_theme| {container::background(Background::from(DELETED_BG_COLOR))}),})} - edit in inflorescence/src/cursor.rs at line 1
pub use inflorescence_view::app::cursor::{LogChangeFileSelection, Msg, Select, Selection, State,}; - edit in inflorescence/src/cursor.rs at line 6
use libflorescence::prelude::*; - edit in inflorescence/src/cursor.rs at line 8
use std::collections::HashMap; - edit in inflorescence/src/cursor.rs at line 9[49.2147]→[34.18884:18885](∅→∅),[35.8825]→[34.18884:18885](∅→∅),[34.18884]→[34.18884:18885](∅→∅),[34.18885]→[5.27:113](∅→∅),[5.26]→[5.27:113](∅→∅),[5.113]→[49.2148:2187](∅→∅),[49.2187]→[51.879:938](∅→∅),[51.938]→[49.2276:2279](∅→∅),[49.2276]→[49.2276:2279](∅→∅),[49.2279]→[34.18886:18903](∅→∅),[5.113]→[34.18886:18903](∅→∅),[34.18903]→[5.137:158](∅→∅),[5.137]→[5.137:158](∅→∅),[5.158]→[34.18904:18965](∅→∅),[35.8854]→[34.19002:19068](∅→∅),[34.19002]→[34.19002:19068](∅→∅),[35.8883]→[37.5872:5879](∅→∅),[37.5879]→[39.12540:12575](∅→∅),[39.12575]→[37.5892:5919](∅→∅),[37.5892]→[37.5892:5919](∅→∅),[37.5919]→[45.20882:20907](∅→∅),[45.20907]→[46.11960:12251](∅→∅),[46.12251]→[39.12576:12622](∅→∅),[45.20907]→[39.12576:12622](∅→∅),[37.5919]→[39.12576:12622](∅→∅),[37.5919]→[34.19105:19112](∅→∅),[35.8883]→[34.19105:19112](∅→∅),[39.12622]→[34.19105:19112](∅→∅),[34.19105]→[34.19105:19112](∅→∅),[34.19112]→[39.12623:12720](∅→∅),[39.12720]→[34.19112:19114](∅→∅),[45.20978]→[34.19112:19114](∅→∅),[34.19112]→[34.19112:19114](∅→∅)
#[derive(Debug, Default)]pub struct State {pub selection: Option<Selection>,}#[derive(Debug, Clone)]pub enum Msg {Down,Up,Right,Left,Select(Select),}#[derive(Debug)]pub enum Selection {UntrackedFile {ix: usize,path: String,},ChangedFile {ix: usize,path: String,},LogChange {ix: usize,hash: pijul::Hash,message: String,/// All the diffs in this change keyed by file path. Loaded async and/// set to None only while loading. The `diff::State` is also/// in here so that is it preserved while navigating between files.diffs: Option<HashMap<String, (diff::File, diff::State)>>,file: Option<LogChangeFileSelection>,},}#[derive(Debug)]pub struct LogChangeFileSelection {pub ix: usize,pub path: String,} - edit in inflorescence/src/cursor.rs at line 10[34.19115]→[34.19115:19157](∅→∅),[34.19157]→[45.20979:21275](∅→∅),[6.47]→[5.188:190](∅→∅),[18.661]→[5.188:190](∅→∅),[40.796]→[5.188:190](∅→∅),[8.1691]→[5.188:190](∅→∅),[22.3191]→[5.188:190](∅→∅),[37.5954]→[5.188:190](∅→∅),[41.6298]→[5.188:190](∅→∅),[45.21275]→[5.188:190](∅→∅),[5.188]→[5.188:190](∅→∅),[5.190]→[49.2280:2281](∅→∅)
#[derive(Debug, Clone)]pub enum Select {UntrackedFile {ix: usize,path: String,},ChangedFile {ix: usize,path: String,},LogChange {ix: usize,hash: pijul::Hash,message: String,},LogChangeFile {ix: usize,path: String,},} - edit in Cargo.toml at line 41
[workspace.dependencies.iced_test]git = "https://github.com/iced-rs/iced"rev = "50cc94d944ada88bf3d7fcd1d2741b7104b9b1d1" - edit in Cargo.lock at line 2397
name = "iced_test"version = "0.14.0-dev"source = "git+https://github.com/iced-rs/iced?rev=50cc94d944ada88bf3d7fcd1d2741b7104b9b1d1#50cc94d944ada88bf3d7fcd1d2741b7104b9b1d1"dependencies = ["iced_renderer","iced_runtime","png","sha2 0.10.8","thiserror 1.0.69",][[package]] - edit in Cargo.lock at line 2734
"iced_test", - replacement in Cargo.lock at line 3578
"proc-macro-crate 3.3.0","proc-macro-crate 1.3.1", - replacement in Cargo.lock at line 5742
"rand 0.8.5","rand 0.7.3",