fix: retain repository status between picker openings
This commit is contained in:
@@ -1,14 +1,3 @@
|
||||
- [x] add option to highlight trailing whitespace (off by default)
|
||||
- [x] add option to put characters at certain whitespace characters (off by default)
|
||||
- whitespace kind -> character map should be configurable.
|
||||
- [x] add option to insert spaces instead tab character when pressing tab:
|
||||
- number of spaces should be configurable (per language)
|
||||
- [x] github/gitlab/gitea integration (`:pull_requests [remote]`)
|
||||
- github pr picker (floating window)
|
||||
- three tabs: open, closed, all
|
||||
- search under the given category
|
||||
- selecting a pr opens the diff viewer for that pr using the branch merge base (standard github diff view on prs; already implemented as default diff viewer behavior in vamp)
|
||||
- [x] move the checkmark in the file picker in the diff viewer right next to the modification type indicator (still left of, but only with a 1 column margin). the modification type indicator should retain it's current position, not be inset further.
|
||||
- [ ] diff view: github integration (code review) - add comments on a pr, add review comments at specific lines of files, request changes, approve pr, etc.
|
||||
- [ ] diff view: local review comments (same features as for github reviews) that allows review of local changes to deliver specific reviews to ai agents used for coding.
|
||||
- design question: would this require a server that the agent can interact with, or would local review files be fine? don't want to use server client stuff, if it would be easier and better to just use local files.
|
||||
|
||||
@@ -90,7 +90,7 @@ The buffer viewport includes a fixed left gutter with right-aligned hybrid line
|
||||
|
||||
Repository gutter work runs off the render/editor thread. Revision/path-validated worker results update marker vectors belonging to ordinary source buffers, not historical snapshots; panes and tabs borrow their own buffer's markers. After text edits, the last completed markers for the same path remain visible until a current-revision result replaces them, rather than disappearing during each asynchronous refresh. The worker compares editor text with Git HEAD or Git-backed jj's single parent, independently of save baselines and index state. The frontend paints green additions, red deletion anchors, and yellow modifications as `▌`, symbols, or number colors; current-line cyan/bold overrides repository colors in number-color mode.
|
||||
|
||||
The same cancellable worker optionally collects normal-picker status, including when line-gutter rendering is disabled. Disk metadata and dirty-buffer overlays use Git HEAD or jj's single parent without snapshotting jj or writing active refs, objects, or indexes. A private baseline index retains its stat cache across polls; conflicted jj parents are projected once per root/parent. Root, baseline, store, or builtin Git conversion-setting changes invalidate that cache. Private queries disable user filters, external diff, and textconv. Request/tab/root checks reject stale picker results; unchanged catalogs retain their ordinary scanner and filtering rules.
|
||||
The same cancellable worker collects normal-picker status for the startup project root, including when line-gutter rendering is disabled and no picker is open. The editor retains the last completed project-wide result across picker closure and shares it with ordinary pickers in every tab; new pickers apply it before their first render. Opening or closing a picker does not restart status work. Normal file rows reserve a fixed marker column even before the first result or when the checkout is clean. Disk metadata and dirty-buffer overlays use Git HEAD or jj's single parent without snapshotting jj or writing active refs, objects, or indexes. A private baseline index retains its stat cache across polls; conflicted jj parents are projected once per root/parent. Root, baseline, store, or builtin Git conversion-setting changes invalidate that cache. Private queries disable user filters, external diff, and textconv. Request/root checks reject stale picker results; unchanged catalogs retain their ordinary scanner and filtering rules.
|
||||
|
||||
## Syntax caches
|
||||
|
||||
|
||||
+2
-2
@@ -319,7 +319,7 @@ The picker includes ordinary buffers from every tab and unnamed buffers as `[No
|
||||
|
||||
Space o opens a floating **Tree** explorer directly in **INSERT/filter** mode, ready for typing; Space e opens or focuses the docked **Tree**, starting in **NORMAL/navigation** mode when new. Both use one catalog and instance. Invoking the other action applies that presentation's configured default while retaining query, selection identities, and surviving folder expansion. Floating Tree invocation enters filtering if not already filtering. These actions are available as `config.core.file_picker` and `config.core.file_explorer` in Normal/Visual and in the command palette.
|
||||
|
||||
Normal pickers and explorers show the same colored **A/M/D/R** markers as the diff file picker: green additions, yellow modifications, red deletions, and cyan renames (`old -> new`). Unchanged files remain visible. Status compares disk and unsaved buffer text with Git `HEAD` or Git-backed jj's single parent `@-`; it works in both Tree and List, even with `git_gutter = "none"`. Missing tracked files remain as deleted entries; opening one uses the usual missing-file behavior. Status refreshes on opening, edits/saves, focus regain, and once per second. Ctrl+r rescans the normal catalog and refreshes status; status polling alone does not rescan for newly created files.
|
||||
Normal pickers and explorers show the same colored **A/M/D/R** markers as the diff file picker: green additions, yellow modifications, red deletions, and cyan renames (`old -> new`). Unchanged files remain visible. Status compares disk and unsaved buffer text with Git `HEAD` or Git-backed jj's single parent `@-`; it works in both Tree and List, even with `git_gutter = "none"`. Missing tracked files remain as deleted entries; opening one uses the usual missing-file behavior. Status loads asynchronously from startup and refreshes on edits/saves, focus regain, and once per second, even while the picker is closed. Opening or reopening a picker uses the latest completed project-wide result without restarting the status query. The marker column always reserves its width, so filenames do not shift while the initial result arrives or when the checkout becomes clean. Ctrl+r rescans the normal catalog and refreshes status; status polling alone does not rescan for newly created files.
|
||||
|
||||
While a docked Tree has focus in **NORMAL/navigation** mode, leader sequences work in ordinary and diff views: Space o replaces the dock with the floating picker, and Space e returns focus to the document like Escape while leaving the dock visible. Invoking Space e again from the document refocuses it. Tree/selection navigation overrides retain priority. In **INSERT/filter** mode and List query fields, printable leader keys remain literal query text rather than triggering editor actions; configured control-key action bindings remain available.
|
||||
|
||||
@@ -420,7 +420,7 @@ A definition whose source range **overlaps an actual added, removed, or replaced
|
||||
|
||||
Use the ordinary **file-picker/file-explorer actions and bindings**: Space `o` opens a floating picker, Space `e` opens/focuses the docked explorer, and Ctrl+w closes the focused picker/explorer and reclaims its width. Remapped bindings work too; Ctrl+e is no longer reserved by the diff. The changed-path catalog retains A/M/D/R markers and the normal compact tree, filtering, folder expansion, and configurable `tree`/`selection` navigation. It includes removed and historical paths without scanning the working directory. Enter toggles a folder or opens the selected comparison file and focuses its first visible text pane: After for added files, otherwise Before. While the explorer is hidden, Tab/Shift+Tab cycles between visible text panes only; a single-pane view keeps its focus.
|
||||
|
||||
File rows show colored **A** (added, green), **M** (modified, yellow), **D** (removed, red), or **R** (renamed, cyan) markers in Tree and List views, including filtered results. Rename rows show `R old/path -> new/path`; directories have no file-status marker. Ordinary file exploration remains unannotated.
|
||||
File rows show colored **A** (added, green), **M** (modified, yellow), **D** (removed, red), or **R** (renamed, cyan) markers in Tree and List views, including filtered results. Rename rows show `R old/path -> new/path`; directories have no file-status marker.
|
||||
|
||||
Mark or unmark a diff file with **`c` while browsing the changed-file tree**, **`:diff.review.toggle`**, or the command-palette action. With the changed-file explorer focused, it toggles the highlighted file without opening it; from a text pane, the command toggles the displayed file. Reviewed files show a green **`✓` immediately left of A/M/D/R, separated by one blank column**, in Tree and List views. The modification indicator keeps its existing indentation; the review marker uses the preceding gutter space instead of adding padding. Diff rows use the selection background and cursor rather than a `>` prefix. Directories and ordinary filesystem pickers cannot be marked. While filtering, including List view, `c` remains query text; use the palette or a control-key binding there. Explicit bindings take precedence over the default browsing shortcut.
|
||||
|
||||
|
||||
+19
-12
@@ -1830,6 +1830,7 @@ pub struct Editor {
|
||||
startup_diagnostic: Option<StartupDiagnostic>,
|
||||
project_root: PathBuf,
|
||||
file_picker: Option<FilePicker>,
|
||||
picker_repository_changes: Vec<crate::repository::WorkingFileChange>,
|
||||
file_explorer_side: DockSide,
|
||||
file_picker_view: FileView,
|
||||
file_explorer_view: FileView,
|
||||
@@ -1958,6 +1959,7 @@ impl Editor {
|
||||
startup_diagnostic: None,
|
||||
project_root: std::env::current_dir()?,
|
||||
file_picker: None,
|
||||
picker_repository_changes: Vec::new(),
|
||||
file_explorer_side: DockSide::Right,
|
||||
file_picker_view: FileView::Tree,
|
||||
file_explorer_view: FileView::Tree,
|
||||
@@ -4790,21 +4792,24 @@ impl Editor {
|
||||
|
||||
pub(crate) fn set_picker_repository_changes(
|
||||
&mut self,
|
||||
tab: usize,
|
||||
root: &Path,
|
||||
changes: Vec<crate::repository::WorkingFileChange>,
|
||||
) -> bool {
|
||||
let picker = if tab == self.tab_id {
|
||||
self.file_picker.as_mut().filter(|_| self.diff.is_none())
|
||||
} else {
|
||||
self.tabs
|
||||
.get_mut(&tab)
|
||||
.and_then(|tab| tab.file_picker.as_mut().filter(|_| tab.diff.is_none()))
|
||||
};
|
||||
let Some(picker) = picker.filter(|picker| picker.root == root) else {
|
||||
if root != self.project_root {
|
||||
return false;
|
||||
};
|
||||
picker.set_repository_changes(changes)
|
||||
}
|
||||
let mut changed = false;
|
||||
for picker in self.file_picker.iter_mut().chain(
|
||||
self.tabs
|
||||
.values_mut()
|
||||
.filter_map(|tab| tab.file_picker.as_mut()),
|
||||
) {
|
||||
if picker.root == root {
|
||||
changed |= picker.set_repository_changes(changes.clone());
|
||||
}
|
||||
}
|
||||
self.picker_repository_changes = changes;
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn file_explorer_side(&self) -> DockSide {
|
||||
@@ -4861,6 +4866,7 @@ impl Editor {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_project_root(&mut self, root: PathBuf) {
|
||||
self.project_root = root;
|
||||
self.picker_repository_changes.clear();
|
||||
}
|
||||
/// Pending decimal prefix for the next Normal/Visual motion or editing action.
|
||||
pub fn count(&self) -> Option<usize> {
|
||||
@@ -5950,7 +5956,8 @@ impl Editor {
|
||||
picker.clear_error();
|
||||
} else {
|
||||
match FilePicker::scan(self.project_root.clone(), presentation, view) {
|
||||
Ok(picker) => {
|
||||
Ok(mut picker) => {
|
||||
picker.set_repository_changes(self.picker_repository_changes.clone());
|
||||
self.file_picker = Some(picker);
|
||||
}
|
||||
Err(error) => {
|
||||
|
||||
@@ -101,7 +101,6 @@ pub struct FilePicker {
|
||||
tree_searching: bool,
|
||||
warning: Option<String>,
|
||||
repository_changes: Vec<WorkingFileChange>,
|
||||
has_repository_changes: bool,
|
||||
has_virtual_entries: bool,
|
||||
}
|
||||
|
||||
@@ -307,7 +306,6 @@ impl FilePicker {
|
||||
tree_searching: false,
|
||||
warning,
|
||||
repository_changes: Vec::new(),
|
||||
has_repository_changes: false,
|
||||
};
|
||||
for root in roots {
|
||||
let tree = picker.compact(root, None);
|
||||
@@ -780,10 +778,6 @@ impl FilePicker {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_repository_changes(&self) -> bool {
|
||||
self.has_repository_changes
|
||||
}
|
||||
|
||||
pub(crate) fn set_repository_changes(&mut self, changes: Vec<WorkingFileChange>) -> bool {
|
||||
if !self.has_virtual_entries && changes == self.repository_changes {
|
||||
return false;
|
||||
@@ -919,7 +913,6 @@ impl FilePicker {
|
||||
replacement.error = self.error.clone();
|
||||
self.replace_catalog(replacement);
|
||||
}
|
||||
self.has_repository_changes = self.nodes.iter().any(|node| node.status.is_some());
|
||||
self.repository_changes = changes;
|
||||
topology_changed || metadata_changed
|
||||
}
|
||||
@@ -1125,7 +1118,6 @@ mod tests {
|
||||
assert_eq!(picker.query, "src");
|
||||
assert!(!picker.focused);
|
||||
assert_eq!(picker.presentation, FilePresentation::Docked);
|
||||
assert!(picker.has_repository_changes());
|
||||
for view in [FileView::List, FileView::Tree] {
|
||||
picker.set_view(view);
|
||||
for query in ["", "src", "modified.rs", "deleted.rs"] {
|
||||
@@ -1166,7 +1158,6 @@ mod tests {
|
||||
picker.reveal_path(Path::new("src/clean.rs"));
|
||||
assert!(picker.set_repository_changes(Vec::new()));
|
||||
assert_eq!(selected_path(&picker), Path::new("src/clean.rs"));
|
||||
assert!(!picker.has_repository_changes());
|
||||
assert_eq!(picker.entries().len(), 4);
|
||||
assert!(picker.nodes.iter().all(|node| !node.virtual_entry));
|
||||
assert!(
|
||||
|
||||
+87
-28
@@ -376,7 +376,7 @@ pub struct EditorSession {
|
||||
gutter_request_id: u64,
|
||||
gutter_pending: bool,
|
||||
last_gutter_refresh: Option<Instant>,
|
||||
picker_source: Option<(usize, PathBuf)>,
|
||||
picker_source: Option<PathBuf>,
|
||||
battery: Option<crate::battery::Battery>,
|
||||
battery_enabled: bool,
|
||||
last_file_refresh: Instant,
|
||||
@@ -1275,15 +1275,8 @@ impl EditorSession {
|
||||
|
||||
fn poll_gutter_at(&mut self, now: Instant) -> bool {
|
||||
let line_gutter = self.editor.gutter_style() != crate::gutter::GutterStyle::None;
|
||||
let picker_changed = match (self.editor.normal_file_picker(), &self.picker_source) {
|
||||
(Some(picker), Some((tab, root))) => {
|
||||
*tab != self.editor.tab_id() || *root != picker.root
|
||||
}
|
||||
(None, None) => false,
|
||||
_ => true,
|
||||
};
|
||||
if self.editor.should_quit() || (!line_gutter && self.editor.normal_file_picker().is_none())
|
||||
{
|
||||
let picker_changed = self.picker_source.as_deref() != Some(self.editor.project_root());
|
||||
if self.editor.should_quit() {
|
||||
self.gutter_worker = None;
|
||||
self.gutter_sources.clear();
|
||||
self.gutter_pending = false;
|
||||
@@ -1320,10 +1313,7 @@ impl EditorSession {
|
||||
.is_none_or(|last| now.saturating_duration_since(last) >= Duration::from_secs(1));
|
||||
if sources_changed || picker_changed || refresh_due {
|
||||
if picker_changed {
|
||||
self.picker_source = self
|
||||
.editor
|
||||
.normal_file_picker()
|
||||
.map(|picker| (self.editor.tab_id(), picker.root.clone()));
|
||||
self.picker_source = Some(self.editor.project_root().to_owned());
|
||||
}
|
||||
if sources_changed {
|
||||
self.gutter_sources = self
|
||||
@@ -1357,11 +1347,6 @@ impl EditorSession {
|
||||
}
|
||||
self.gutter_request_id = self.gutter_request_id.wrapping_add(1);
|
||||
self.last_gutter_refresh = Some(now);
|
||||
if self.gutter_sources.is_empty() && self.picker_source.is_none() {
|
||||
self.gutter_worker = None;
|
||||
self.gutter_pending = false;
|
||||
return false;
|
||||
}
|
||||
if self.gutter_worker.is_none() {
|
||||
match crate::gutter::Worker::start() {
|
||||
Ok(worker) => self.gutter_worker = Some(worker),
|
||||
@@ -1383,7 +1368,7 @@ impl EditorSession {
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let picker = self.picker_source.as_ref().map(|(_, root)| {
|
||||
let picker = self.picker_source.as_ref().map(|root| {
|
||||
let buffers = self
|
||||
.gutter_sources
|
||||
.iter()
|
||||
@@ -1451,20 +1436,15 @@ impl EditorSession {
|
||||
}
|
||||
|
||||
fn apply_picker_result(&mut self, id: u64, reply: crate::gutter::PickerChanges) -> bool {
|
||||
let Some((tab, root)) = &self.picker_source else {
|
||||
let Some(root) = &self.picker_source else {
|
||||
return false;
|
||||
};
|
||||
if id != self.gutter_request_id
|
||||
|| *tab != self.editor.tab_id()
|
||||
|| *root != reply.root
|
||||
|| self
|
||||
.editor
|
||||
.normal_file_picker()
|
||||
.is_none_or(|picker| picker.root != *root)
|
||||
|| *root != self.editor.project_root()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let tab = *tab;
|
||||
let mut changed = false;
|
||||
let changes = match reply.result {
|
||||
Ok(changes) => changes,
|
||||
@@ -1477,7 +1457,7 @@ impl EditorSession {
|
||||
changed
|
||||
| self
|
||||
.editor
|
||||
.set_picker_repository_changes(tab, &reply.root, changes)
|
||||
.set_picker_repository_changes(&reply.root, changes)
|
||||
}
|
||||
|
||||
pub(crate) fn refresh_gutter(&mut self) -> bool {
|
||||
@@ -1816,6 +1796,16 @@ mod tests {
|
||||
session.handle(Input::Char(' '));
|
||||
session.handle(Input::Char('o'));
|
||||
};
|
||||
let status = |session: &EditorSession| {
|
||||
session
|
||||
.editor
|
||||
.normal_file_picker()
|
||||
.unwrap()
|
||||
.entries()
|
||||
.find(|row| row.path == Path::new("buffer.txt"))
|
||||
.unwrap()
|
||||
.status
|
||||
};
|
||||
let ready = |session: &mut EditorSession, clock: Instant, expected| {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
@@ -1845,16 +1835,56 @@ mod tests {
|
||||
thread::sleep(Duration::from_millis(5));
|
||||
}
|
||||
};
|
||||
let settle = |session: &mut EditorSession| {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
session.poll_gutter_at(now);
|
||||
if !session.gutter_pending {
|
||||
break;
|
||||
}
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"background file status did not settle"
|
||||
);
|
||||
thread::sleep(Duration::from_millis(5));
|
||||
}
|
||||
};
|
||||
settle(&mut session);
|
||||
open(&mut session);
|
||||
assert_eq!(
|
||||
status(&session),
|
||||
Some(FileStatus::Modified),
|
||||
"first open must reuse status loaded while the picker was closed",
|
||||
);
|
||||
ready(&mut session, now, Some(FileStatus::Modified));
|
||||
session.handle(Input::Ctrl('w'));
|
||||
settle(&mut session);
|
||||
open(&mut session);
|
||||
assert_eq!(
|
||||
status(&session),
|
||||
Some(FileStatus::Modified),
|
||||
"reopening must retain completed status",
|
||||
);
|
||||
let request = session.gutter_request_id;
|
||||
session.poll_gutter_at(now);
|
||||
assert_eq!(
|
||||
session.gutter_request_id, request,
|
||||
"opening must not restart status work"
|
||||
);
|
||||
session.handle(Input::Ctrl('w'));
|
||||
session.poll_gutter_at(now);
|
||||
session
|
||||
.editor
|
||||
.apply(vec![Effect::Replace("base\n".into())])
|
||||
.unwrap();
|
||||
assert!(session.editor.dirty());
|
||||
settle(&mut session);
|
||||
open(&mut session);
|
||||
assert_eq!(
|
||||
status(&session),
|
||||
None,
|
||||
"closed-picker edits must refresh the cache"
|
||||
);
|
||||
ready(&mut session, now, None);
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("buffer.txt")).unwrap(),
|
||||
@@ -1863,11 +1893,40 @@ mod tests {
|
||||
session.handle(Input::Ctrl('w'));
|
||||
session.poll_gutter_at(now);
|
||||
session.handle(Input::Char('u'));
|
||||
settle(&mut session);
|
||||
open(&mut session);
|
||||
assert_eq!(status(&session), Some(FileStatus::Modified));
|
||||
ready(&mut session, now, Some(FileStatus::Modified));
|
||||
git(&["add", "buffer.txt"]);
|
||||
git(&["commit", "-qm", "new baseline"]);
|
||||
ready(&mut session, now + Duration::from_secs(1), None);
|
||||
assert!(!session.apply_picker_result(
|
||||
session.gutter_request_id.wrapping_sub(1),
|
||||
crate::gutter::PickerChanges {
|
||||
root: fixture.0.clone(),
|
||||
result: Ok(Vec::new()),
|
||||
},
|
||||
));
|
||||
assert!(!session.apply_picker_result(
|
||||
session.gutter_request_id,
|
||||
crate::gutter::PickerChanges {
|
||||
root: fixture.0.join("elsewhere"),
|
||||
result: Ok(Vec::new()),
|
||||
},
|
||||
));
|
||||
session.handle(Input::Ctrl('w'));
|
||||
session.execute("tab.new");
|
||||
open(&mut session);
|
||||
assert_eq!(status(&session), None);
|
||||
assert!(
|
||||
session
|
||||
.editor
|
||||
.normal_file_picker()
|
||||
.unwrap()
|
||||
.entries()
|
||||
.any(|row| row.path == Path::new("added.txt")
|
||||
&& row.status == Some(FileStatus::Added))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+57
-10
@@ -4649,7 +4649,6 @@ fn draw_file_list(
|
||||
);
|
||||
}
|
||||
let entries = picker.entries();
|
||||
let normal_markers = changed_files.is_none() && picker.has_repository_changes();
|
||||
if entries.len() == 0 {
|
||||
frame.render_widget(
|
||||
Paragraph::new(if picker.query.is_empty() {
|
||||
@@ -4722,7 +4721,7 @@ fn draw_file_list(
|
||||
Style::default().fg(color),
|
||||
));
|
||||
spans.push(Span::raw(" "));
|
||||
} else if normal_markers {
|
||||
} else if changed_files.is_none() {
|
||||
spans.push(Span::raw(" "));
|
||||
}
|
||||
if status == Some(FileStatus::Renamed)
|
||||
@@ -7559,7 +7558,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
picker.refresh();
|
||||
assert!(picker.has_repository_changes());
|
||||
assert!(
|
||||
picker
|
||||
.entries()
|
||||
@@ -7581,13 +7579,57 @@ mod tests {
|
||||
})
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("> clean.txt"));
|
||||
assert!(text.contains("r.txt"));
|
||||
assert!(!text.contains("old�name.txt"));
|
||||
assert!(!text.contains("d.txt"));
|
||||
assert!(!text.contains('✓'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_file_names_stay_put_when_status_arrives_or_clears() {
|
||||
let directory = Directory::new();
|
||||
directory.file("file.txt", "");
|
||||
let mut picker = FilePicker::scan(
|
||||
directory.0.clone(),
|
||||
FilePresentation::Floating,
|
||||
FileView::List,
|
||||
)
|
||||
.unwrap();
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(80, 12)).unwrap();
|
||||
for view in [FileView::List, FileView::Tree] {
|
||||
picker.set_view(view);
|
||||
let mut columns = Vec::new();
|
||||
for status in [None, Some(FileStatus::Modified), None] {
|
||||
picker.set_repository_changes(
|
||||
status
|
||||
.into_iter()
|
||||
.map(|status| crate::repository::WorkingFileChange {
|
||||
path: "file.txt".into(),
|
||||
status,
|
||||
old_path: None,
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_file_list(
|
||||
frame,
|
||||
&picker,
|
||||
frame.area(),
|
||||
"",
|
||||
&mut WheelPanels::default(),
|
||||
None,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
columns.push(text.lines().find_map(|row| row.find("file.txt")).unwrap());
|
||||
}
|
||||
assert_eq!(columns[0], columns[1], "status arrival moved the filename");
|
||||
assert_eq!(columns[1], columns[2], "clearing status moved the filename");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_status_prefix_clips_without_review_columns_or_painting_outside_tiny_bounds() {
|
||||
let directory = Directory::new();
|
||||
@@ -13167,7 +13209,7 @@ end
|
||||
.draw(|frame| draw_file_list(frame, &picker, area, "", &mut panels, None))
|
||||
.unwrap();
|
||||
assert!(panels.manual_files.is_none());
|
||||
assert!(screen_text(&terminal).contains("> file-10"));
|
||||
assert!(screen_text(&terminal).contains("file-10"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -13306,7 +13348,11 @@ end
|
||||
})
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains(&format!("> {label}")), "{text}");
|
||||
assert!(
|
||||
text.lines()
|
||||
.any(|row| row.contains(label) && row.contains('>')),
|
||||
"{text}"
|
||||
);
|
||||
let buffer = terminal.backend().buffer();
|
||||
let selected_y = (3..8).find(|&y| buffer[(1, y)].symbol() == ">").unwrap();
|
||||
assert_eq!(buffer[(1, selected_y)].bg, Color::Blue);
|
||||
@@ -13451,8 +13497,10 @@ end
|
||||
let selected_y = (area.y + 2..area.bottom() - 1)
|
||||
.find(|&y| buffer[(area.x + 1, y)].symbol() == ">")
|
||||
.unwrap();
|
||||
assert_eq!(buffer[(area.x + 3, selected_y)].symbol(), "界");
|
||||
assert_eq!(buffer[(area.x + 5, selected_y)].symbol(), "e\u{301}");
|
||||
let selected_x = (area.x + 2..area.right())
|
||||
.find(|&x| buffer[(x, selected_y)].symbol() == "界")
|
||||
.unwrap();
|
||||
assert_eq!(buffer[(selected_x + 2, selected_y)].symbol(), "e\u{301}");
|
||||
assert_eq!(buffer[(area.x + 1, selected_y)].bg, Color::Blue);
|
||||
assert!(
|
||||
buffer[(area.x + 1, selected_y)]
|
||||
@@ -13729,7 +13777,6 @@ end
|
||||
" nested/",
|
||||
" inner.txt",
|
||||
" sibling.txt",
|
||||
"> single/",
|
||||
" only.txt",
|
||||
" root.txt",
|
||||
] {
|
||||
@@ -13833,7 +13880,7 @@ end
|
||||
}
|
||||
if width == 100 {
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("> file.txt"), "{text}");
|
||||
assert!(text.contains("file.txt"), "{text}");
|
||||
assert!(!text.contains("dir-00"), "{text}");
|
||||
}
|
||||
for query in ["missing", long_query.as_str()] {
|
||||
|
||||
Reference in New Issue
Block a user