feat(display): status messages and the view title show displays, keys keep addressing (improvise-cq1q.13)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
committed by
Edward Langley
parent
09fa78e7cd
commit
e0bd0be3d5
@@ -494,6 +494,24 @@ mod tests {
|
|||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_refusal_names_the_formulas_display_not_its_key() {
|
||||||
|
let mut app = formula_app();
|
||||||
|
let measure = app.model_state.workbook.model.category_mut("_Measure");
|
||||||
|
let measure = measure.unwrap();
|
||||||
|
measure.add_item("Profit");
|
||||||
|
measure.set_item_display("Profit", "Operating profit after all costs");
|
||||||
|
app.cursor_mut().selected = (1, 2);
|
||||||
|
press(&mut app, 'p');
|
||||||
|
assert!(
|
||||||
|
app.view_state
|
||||||
|
.status_msg
|
||||||
|
.contains("Operating profit after all costs is computed"),
|
||||||
|
"got {:?}",
|
||||||
|
app.view_state.status_msg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// improvise-54g1 — see context/test-evidence/command.md.
|
/// improvise-54g1 — see context/test-evidence/command.md.
|
||||||
#[test]
|
#[test]
|
||||||
fn p_onto_a_formula_owned_cell_writes_nothing_and_names_the_formula() {
|
fn p_onto_a_formula_owned_cell_writes_nothing_and_names_the_formula() {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ pub const INSIDE_A_DRILL_REFUSAL: &str =
|
|||||||
"A value is not staged inside a drill — edit the cell instead";
|
"A value is not staged inside a drill — edit the cell instead";
|
||||||
|
|
||||||
/// What a command says when the cell it would write is a formula's result.
|
/// What a command says when the cell it would write is a formula's result.
|
||||||
pub fn derived_refusal(target: &str) -> String {
|
pub fn derived_refusal(model: &Model, target: &str) -> String {
|
||||||
|
let target = model.item_display("_Measure", target);
|
||||||
format!("{target} is computed by a formula — edit the formula, not the cell")
|
format!("{target} is computed by a formula — edit the formula, not the cell")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +39,10 @@ pub fn uneditable(model: &Model, layout: &GridLayout, row: usize, col: usize) ->
|
|||||||
}
|
}
|
||||||
.to_string(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
Some(CellId::Derived { target }) => Some(derived_refusal(&target)),
|
Some(CellId::Derived { target }) => Some(derived_refusal(model, &target)),
|
||||||
Some(CellId::NonItem { category, item }) => Some(non_item_refusal(&category, &item)),
|
Some(CellId::NonItem { category, item }) => {
|
||||||
|
Some(non_item_refusal(model.category_display(&category), &item))
|
||||||
|
}
|
||||||
Some(CellId::Record(_) | CellId::Create(_) | CellId::Coordinate { .. }) | None => None,
|
Some(CellId::Record(_) | CellId::Create(_) | CellId::Coordinate { .. }) | None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,11 +132,14 @@ impl<'a> CmdContext<'a> {
|
|||||||
match self.cursor_cell_id() {
|
match self.cursor_cell_id() {
|
||||||
Some(CellId::Record(_)) => self.stored_cell_key(),
|
Some(CellId::Record(_)) => self.stored_cell_key(),
|
||||||
Some(CellId::Aggregate(_)) => Err(self.aggregate_refusal()),
|
Some(CellId::Aggregate(_)) => Err(self.aggregate_refusal()),
|
||||||
Some(CellId::Derived { target }) => Err(derived_refusal(&target)),
|
Some(CellId::Derived { target }) => Err(derived_refusal(self.model, &target)),
|
||||||
Some(CellId::Create(_) | CellId::Coordinate { .. }) => {
|
Some(CellId::Create(_) | CellId::Coordinate { .. }) => {
|
||||||
Err(NO_VALUE_REFUSAL.to_string())
|
Err(NO_VALUE_REFUSAL.to_string())
|
||||||
}
|
}
|
||||||
Some(CellId::NonItem { category, item }) => Err(non_item_refusal(&category, &item)),
|
Some(CellId::NonItem { category, item }) => Err(non_item_refusal(
|
||||||
|
self.model.category_display(&category),
|
||||||
|
&item,
|
||||||
|
)),
|
||||||
None => Err(match self.cell_key() {
|
None => Err(match self.cell_key() {
|
||||||
Some(_) => NO_VALUE_REFUSAL,
|
Some(_) => NO_VALUE_REFUSAL,
|
||||||
None => NO_CELL,
|
None => NO_CELL,
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ impl Cmd for EditOrDrill {
|
|||||||
// the synthetic key names no measure, so the record's key is resolved.
|
// the synthetic key names no measure, so the record's key is resolved.
|
||||||
if let Some(formula) = ctx.owning_formula() {
|
if let Some(formula) = ctx.owning_formula() {
|
||||||
return vec![effect::refuse(super::core::derived_refusal(
|
return vec![effect::refuse(super::core::derived_refusal(
|
||||||
|
ctx.model,
|
||||||
&formula.target,
|
&formula.target,
|
||||||
))];
|
))];
|
||||||
}
|
}
|
||||||
@@ -393,7 +394,8 @@ impl Cmd for EditOrDrill {
|
|||||||
// refused later, under a minibuffer that hides why (improvise-yc9r).
|
// refused later, under a minibuffer that hides why (improvise-yc9r).
|
||||||
if let Some(improvise_protocol::CellId::NonItem { category, item }) = ctx.cursor_cell_id() {
|
if let Some(improvise_protocol::CellId::NonItem { category, item }) = ctx.cursor_cell_id() {
|
||||||
return vec![effect::refuse(improvise_protocol::non_item_refusal(
|
return vec![effect::refuse(improvise_protocol::non_item_refusal(
|
||||||
&category, &item,
|
ctx.model.category_display(&category),
|
||||||
|
&item,
|
||||||
))];
|
))];
|
||||||
}
|
}
|
||||||
vec![
|
vec![
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use improvise_protocol::viewmodel::axis_change_status;
|
use improvise_protocol::viewmodel::{
|
||||||
|
axis_change_status, deleted_category_status, deleted_item_status, filter_status,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ui::app::AppMode;
|
use crate::ui::app::AppMode;
|
||||||
use crate::ui::effect::{self, Effect, Panel, ViewTarget};
|
use crate::ui::effect::{self, Effect, Panel, ViewTarget};
|
||||||
@@ -186,6 +188,53 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// improvise-cq1q.13: evidence in `context/test-evidence/core.md`.
|
||||||
|
#[test]
|
||||||
|
fn a_panel_status_names_what_a_person_sees_not_the_keys() {
|
||||||
|
const TYPE: &str = "Kind of food and drink";
|
||||||
|
const FOOD: &str = "Foodstuffs of every sort";
|
||||||
|
let mut wb = two_cat_model();
|
||||||
|
let ty = wb.model.category_mut("Type").unwrap();
|
||||||
|
ty.set_display(TYPE);
|
||||||
|
ty.set_item_display("Food", FOOD);
|
||||||
|
let layout = make_layout(&wb);
|
||||||
|
let reg = make_registry();
|
||||||
|
let mut ctx = make_ctx(&wb, &layout, ®);
|
||||||
|
let expanded = std::collections::HashSet::from(["Type".to_string()]);
|
||||||
|
ctx.expanded_cats = &expanded;
|
||||||
|
let said = |effects: &[Box<dyn Effect>]| format!("{:?}", effects.last().unwrap());
|
||||||
|
|
||||||
|
ctx.cat_panel_cursor = 3;
|
||||||
|
assert_eq!(ctx.cat_at_cursor().as_deref(), Some("Type"), "setup");
|
||||||
|
assert!(said(&CycleAxisAtCursor.execute(&ctx)).contains(&format!("{TYPE} → ")));
|
||||||
|
assert!(said(&DeleteCategoryAtCursor.execute(&ctx)).contains(&format!("'{TYPE}'")));
|
||||||
|
let prompt = format!("{:?}", OpenItemAddAtCursor.execute(&ctx));
|
||||||
|
assert!(
|
||||||
|
prompt.contains(&format!("add item to {TYPE}: ")),
|
||||||
|
"{prompt}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
prompt.contains("category: \"Type\""),
|
||||||
|
"the key stays: {prompt}"
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.cat_panel_cursor = 4;
|
||||||
|
let filter = said(&FilterToItem.execute(&ctx));
|
||||||
|
assert!(
|
||||||
|
filter.contains(&format!("Filter: {TYPE} = {FOOD}")),
|
||||||
|
"{filter}"
|
||||||
|
);
|
||||||
|
let delete = format!("{:?}", DeleteCategoryAtCursor.execute(&ctx));
|
||||||
|
assert!(
|
||||||
|
delete.contains(&format!("Deleted item '{FOOD}' from '{TYPE}'")),
|
||||||
|
"{delete}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
delete.contains("item: \"Food\""),
|
||||||
|
"the effect still names the key: {delete}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cycle_panel_focus_with_no_panels_open() {
|
fn cycle_panel_focus_with_no_panels_open() {
|
||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
@@ -718,8 +767,12 @@ impl Cmd for CycleAxisAtCursor {
|
|||||||
ViewTarget::Active,
|
ViewTarget::Active,
|
||||||
cat_name.clone(),
|
cat_name.clone(),
|
||||||
))];
|
))];
|
||||||
effects
|
effects.extend(cycled.map(|axis| {
|
||||||
.extend(cycled.map(|axis| effect::set_status(axis_change_status(&cat_name, axis))));
|
effect::set_status(axis_change_status(
|
||||||
|
ctx.model.category_display(&cat_name),
|
||||||
|
axis,
|
||||||
|
))
|
||||||
|
}));
|
||||||
effects
|
effects
|
||||||
} else {
|
} else {
|
||||||
vec![effect::refuse(
|
vec![effect::refuse(
|
||||||
@@ -738,7 +791,10 @@ impl Cmd for OpenItemAddAtCursor {
|
|||||||
}
|
}
|
||||||
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
||||||
if let Some(cat_name) = ctx.cat_at_cursor() {
|
if let Some(cat_name) = ctx.cat_at_cursor() {
|
||||||
vec![effect::change_mode(AppMode::item_add(cat_name))]
|
let shown = ctx.model.category_display(&cat_name).to_string();
|
||||||
|
vec![effect::change_mode(AppMode::item_add_shown(
|
||||||
|
cat_name, &shown,
|
||||||
|
))]
|
||||||
} else {
|
} else {
|
||||||
vec![effect::refuse(
|
vec![effect::refuse(
|
||||||
"No category selected. Press n to add a category first.",
|
"No category selected. Press n to add a category first.",
|
||||||
@@ -790,7 +846,10 @@ impl Cmd for FilterToItem {
|
|||||||
category: cat_name.clone(),
|
category: cat_name.clone(),
|
||||||
item: item_name.clone(),
|
item: item_name.clone(),
|
||||||
}),
|
}),
|
||||||
effect::set_status(format!("Filter: {cat_name} = {item_name}")),
|
effect::set_status(filter_status(
|
||||||
|
ctx.model.category_display(&cat_name),
|
||||||
|
ctx.model.item_display(&cat_name, &item_name),
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Some(CatTreeEntry::Category { .. }) => {
|
Some(CatTreeEntry::Category { .. }) => {
|
||||||
@@ -816,7 +875,7 @@ impl Cmd for DeleteCategoryAtCursor {
|
|||||||
Some(CatTreeEntry::Category { name, .. }) => {
|
Some(CatTreeEntry::Category { name, .. }) => {
|
||||||
vec![
|
vec![
|
||||||
Box::new(effect::RemoveCategory(name.clone())),
|
Box::new(effect::RemoveCategory(name.clone())),
|
||||||
effect::set_status(format!("Deleted category '{name}'")),
|
effect::set_status(deleted_category_status(ctx.model.category_display(&name))),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Some(CatTreeEntry::Item {
|
Some(CatTreeEntry::Item {
|
||||||
@@ -829,7 +888,10 @@ impl Cmd for DeleteCategoryAtCursor {
|
|||||||
category: cat_name.clone(),
|
category: cat_name.clone(),
|
||||||
item: item_name.clone(),
|
item: item_name.clone(),
|
||||||
}),
|
}),
|
||||||
effect::set_status(format!("Deleted item '{item_name}' from '{cat_name}'")),
|
effect::set_status(deleted_item_status(
|
||||||
|
ctx.model.item_display(&cat_name, &item_name),
|
||||||
|
ctx.model.category_display(&cat_name),
|
||||||
|
)),
|
||||||
];
|
];
|
||||||
effects.extend(retreat_from_deleted_item(ctx));
|
effects.extend(retreat_from_deleted_item(ctx));
|
||||||
effects
|
effects
|
||||||
|
|||||||
@@ -8,6 +8,28 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::command::cmd::test_helpers::*;
|
use crate::command::cmd::test_helpers::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_tile_status_names_the_display_not_the_key() {
|
||||||
|
let mut m = two_cat_model();
|
||||||
|
m.model
|
||||||
|
.category_mut("Type")
|
||||||
|
.unwrap()
|
||||||
|
.set_display("Kind of food and drink");
|
||||||
|
let layout = make_layout(&m);
|
||||||
|
let reg = make_registry();
|
||||||
|
let mut ctx = make_ctx(&m, &layout, ®);
|
||||||
|
ctx.tile_cat_idx = ctx
|
||||||
|
.view
|
||||||
|
.tileable_cats(&ctx.model.category_names())
|
||||||
|
.iter()
|
||||||
|
.position(|c| *c == "Type")
|
||||||
|
.unwrap();
|
||||||
|
let effects = TileAxisOp { axis: None }.execute(&ctx);
|
||||||
|
let said = format!("{:?}", effects.last().unwrap());
|
||||||
|
assert!(said.contains("Kind of food and drink → "), "{said}");
|
||||||
|
assert!(!said.contains("Type →"), "the key was said: {said}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tile_axis_cycle_produces_cycle_effect() {
|
fn tile_axis_cycle_produces_cycle_effect() {
|
||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
@@ -161,7 +183,10 @@ impl Cmd for TileAxisOp {
|
|||||||
}),
|
}),
|
||||||
None => Box::new(effect::CycleAxis(ViewTarget::Active, name.to_string())),
|
None => Box::new(effect::CycleAxis(ViewTarget::Active, name.to_string())),
|
||||||
};
|
};
|
||||||
let status = format!("{} → {}", name, new_axis.plain_label());
|
let status = improvise_protocol::viewmodel::axis_change_status(
|
||||||
|
ctx.model.category_display(name),
|
||||||
|
new_axis,
|
||||||
|
);
|
||||||
vec![axis_effect, effect::set_status(status)]
|
vec![axis_effect, effect::set_status(status)]
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
|||||||
@@ -332,12 +332,9 @@ impl Grids<'_> {
|
|||||||
pub fn unwritable(&self, view: &str, cell: &RecordCell) -> Option<String> {
|
pub fn unwritable(&self, view: &str, cell: &RecordCell) -> Option<String> {
|
||||||
let key = self.resolve_cell(view, cell)?;
|
let key = self.resolve_cell(view, cell)?;
|
||||||
let model = &self.app.model_state.workbook.model;
|
let model = &self.app.model_state.workbook.model;
|
||||||
let derived = model.formula_for(&key).map(|f| {
|
let derived = model
|
||||||
format!(
|
.formula_for(&key)
|
||||||
"{} is computed by a formula — edit the formula, not the cell",
|
.map(|f| crate::command::cmd::core::derived_refusal(model, &f.target));
|
||||||
f.target
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let refusal = self.app.non_item_refusal(&key).or(derived);
|
let refusal = self.app.non_item_refusal(&key).or(derived);
|
||||||
refusal.or_else(|| self.aggregate_refusal(view, cell.record, &key))
|
refusal.or_else(|| self.aggregate_refusal(view, cell.record, &key))
|
||||||
}
|
}
|
||||||
@@ -772,9 +769,9 @@ impl App {
|
|||||||
/// (improvise-b114, improvise-nacd). See `context/code/command.md`.
|
/// (improvise-b114, improvise-nacd). See `context/code/command.md`.
|
||||||
fn non_item_refusal(&self, key: &CellKey) -> Option<String> {
|
fn non_item_refusal(&self, key: &CellKey) -> Option<String> {
|
||||||
let model = &self.model_state.workbook.model;
|
let model = &self.model_state.workbook.model;
|
||||||
model
|
model.first_non_item(key).map(|(cat, name)| {
|
||||||
.first_non_item(key)
|
improvise_protocol::non_item_refusal(model.category_display(cat), name)
|
||||||
.map(|(cat, name)| improvise_protocol::non_item_refusal(cat, name))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Point **this client's** slice at `name`, if the document has such a view,
|
/// Point **this client's** slice at `name`, if the document has such a view,
|
||||||
@@ -5620,6 +5617,22 @@ mod non_item_key_tests {
|
|||||||
assert_eq!(app.view_state.status_msg, refusal("Mars"));
|
assert_eq!(app.view_state.status_msg, refusal("Mars"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_non_item_refusal_names_the_categorys_display() {
|
||||||
|
let mut app = paged_app("Mars");
|
||||||
|
app.model_state
|
||||||
|
.workbook
|
||||||
|
.model
|
||||||
|
.category_mut("Region")
|
||||||
|
.unwrap()
|
||||||
|
.set_display("Sales territory of the whole company");
|
||||||
|
press(&mut app, "i");
|
||||||
|
assert_eq!(
|
||||||
|
app.view_state.status_msg,
|
||||||
|
improvise_protocol::non_item_refusal("Sales territory of the whole company", "Mars"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// improvise-yc9r: `p` on a non-item cell writes no orphan. Evidence file.
|
/// improvise-yc9r: `p` on a non-item cell writes no orphan. Evidence file.
|
||||||
#[test]
|
#[test]
|
||||||
fn p_on_a_non_item_cell_writes_nothing() {
|
fn p_on_a_non_item_cell_writes_nothing() {
|
||||||
|
|||||||
@@ -402,7 +402,14 @@ impl AppMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn item_add(category: String) -> Self {
|
pub fn item_add(category: String) -> Self {
|
||||||
let prompt = format!("add item to {category}: ");
|
let shown = category.clone();
|
||||||
|
Self::item_add_shown(category, &shown)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The prompt names the category by what a person sees; `category` stays the
|
||||||
|
/// key the commit adds the item to (improvise-cq1q.13).
|
||||||
|
pub fn item_add_shown(category: String, shown: &str) -> Self {
|
||||||
|
let prompt = format!("add item to {shown}: ");
|
||||||
Self::ItemAdd {
|
Self::ItemAdd {
|
||||||
category,
|
category,
|
||||||
minibuf: MinibufferConfig {
|
minibuf: MinibufferConfig {
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ pub enum CellId {
|
|||||||
|
|
||||||
/// Why a cell whose coordinate names no item takes no write. One wording for the
|
/// Why a cell whose coordinate names no item takes no write. One wording for the
|
||||||
/// projection's decline and the host's refusal (improvise-gk51).
|
/// projection's decline and the host's refusal (improvise-gk51).
|
||||||
|
/// `category` is what a person sees for it; `item` is the stray coordinate as
|
||||||
|
/// stored, which has no display because it names no item.
|
||||||
pub fn non_item_refusal(category: &str, item: &str) -> String {
|
pub fn non_item_refusal(category: &str, item: &str) -> String {
|
||||||
format!("{item} is no item of {category}, so there is no one cell to write")
|
format!("{item} is no item of {category}, so there is no one cell to write")
|
||||||
}
|
}
|
||||||
@@ -838,6 +840,19 @@ impl CategoryTreeCache {
|
|||||||
pub fn category(&self, name: &str) -> Option<&CategoryRow> {
|
pub fn category(&self, name: &str) -> Option<&CategoryRow> {
|
||||||
self.categories.iter().find(|c| c.name == name)
|
self.categories.iter().find(|c| c.name == name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a person sees for a category key; the key when the cache lacks it.
|
||||||
|
/// For a message only: never pass the result to a lookup (core.md §21).
|
||||||
|
pub fn category_display<'a>(&'a self, name: &'a str) -> &'a str {
|
||||||
|
self.category(name).map_or(name, |c| c.display.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What a person sees for an item of `cat`; the key when either is absent.
|
||||||
|
pub fn item_display<'a>(&'a self, cat: &str, item: &'a str) -> &'a str {
|
||||||
|
self.category(cat)
|
||||||
|
.and_then(|c| c.items.iter().find(|i| i.name == item))
|
||||||
|
.map_or(item, |i| i.display.as_str())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Three mutations, because the effects are three *shapes*. `Items` and
|
/// Three mutations, because the effects are three *shapes*. `Items` and
|
||||||
@@ -955,6 +970,17 @@ pub struct ViewListCache {
|
|||||||
pub views: Vec<ViewSummary>,
|
pub views: Vec<ViewSummary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ViewListCache {
|
||||||
|
/// What a person sees for a view key; the key when the list lacks it.
|
||||||
|
/// For a title or a message only, never a lookup (core.md §21).
|
||||||
|
pub fn display_of<'a>(&'a self, key: &'a str) -> &'a str {
|
||||||
|
self.views
|
||||||
|
.iter()
|
||||||
|
.find(|v| v.name == key)
|
||||||
|
.map_or(key, |v| v.display.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WidgetCache for ViewListCache {
|
impl WidgetCache for ViewListCache {
|
||||||
type Update = Vec<ViewSummary>;
|
type Update = Vec<ViewSummary>;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::client::{ClientState, Opened, OpenedCell, OpenedTarget};
|
use crate::client::{ClientState, Opened, OpenedCell, OpenedTarget};
|
||||||
use crate::frame::ClientFrame;
|
use crate::frame::ClientFrame;
|
||||||
use crate::keymap::{Binding, Keymaps, ModeKey};
|
use crate::keymap::{Binding, Keymaps, ModeKey};
|
||||||
use crate::viewmodel::{CatTreeEntry, axis_change_status, flatten_cat_tree};
|
use crate::viewmodel::{
|
||||||
|
CatTreeEntry, axis_change_status, deleted_category_status, deleted_item_status, filter_status,
|
||||||
|
flatten_cat_tree,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
AppMode, CellId, CellValue, Command, CreateKind, FormulaId, FormulaSlot, GridExtent, HelpPage,
|
AppMode, CellId, CellValue, Command, CreateKind, FormulaId, FormulaSlot, GridExtent, HelpPage,
|
||||||
KeyCode, KeyEvent, ModelCommand, Panel, RECORD_COORDS_CANNOT_BE_EMPTY, RecordCell, ViewState,
|
KeyCode, KeyEvent, ModelCommand, Panel, RECORD_COORDS_CANNOT_BE_EMPTY, RecordCell, ViewState,
|
||||||
@@ -579,8 +582,9 @@ fn composed(
|
|||||||
category: cat.clone(),
|
category: cat.clone(),
|
||||||
},
|
},
|
||||||
)?];
|
)?];
|
||||||
|
let shown = state.categories.category_display(&cat);
|
||||||
commands
|
commands
|
||||||
.extend(cycled.map(|axis| Command::SetStatus(axis_change_status(&cat, axis))));
|
.extend(cycled.map(|axis| Command::SetStatus(axis_change_status(shown, axis))));
|
||||||
commands
|
commands
|
||||||
}
|
}
|
||||||
None => vec![Command::SetStatus(
|
None => vec![Command::SetStatus(
|
||||||
@@ -592,7 +596,10 @@ fn composed(
|
|||||||
"delete-formula-at-cursor" => delete_formula_at_cursor(state),
|
"delete-formula-at-cursor" => delete_formula_at_cursor(state),
|
||||||
|
|
||||||
"open-item-add-at-cursor" => Some(match cat_at_cursor(state) {
|
"open-item-add-at-cursor" => Some(match cat_at_cursor(state) {
|
||||||
Some(cat) => vec![Command::ChangeMode(AppMode::item_add(cat))],
|
Some(cat) => {
|
||||||
|
let shown = state.categories.category_display(&cat).to_string();
|
||||||
|
vec![Command::ChangeMode(AppMode::item_add_shown(cat, &shown))]
|
||||||
|
}
|
||||||
None => vec![Command::SetStatus(
|
None => vec![Command::SetStatus(
|
||||||
"No category selected. Press n to add a category first.".to_string(),
|
"No category selected. Press n to add a category first.".to_string(),
|
||||||
)],
|
)],
|
||||||
@@ -956,12 +963,14 @@ fn edit_or_drill(state: &ClientState, mode: AppMode) -> Option<Vec<Command>> {
|
|||||||
"an aggregate is edited by drilling in — {}",
|
"an aggregate is edited by drilling in — {}",
|
||||||
not_yet("drill")
|
not_yet("drill")
|
||||||
))]),
|
))]),
|
||||||
CellId::Derived { target } => Some(vec![Command::SetStatus(derived_decline(target))]),
|
CellId::Derived { target } => {
|
||||||
|
Some(vec![Command::SetStatus(derived_decline(state, target))])
|
||||||
|
}
|
||||||
// Both creating columns are records-only, so the guard above takes
|
// Both creating columns are records-only, so the guard above takes
|
||||||
// them; declining rather than `None` keeps that a door, not a hole.
|
// them; declining rather than `None` keeps that a door, not a hole.
|
||||||
CellId::Create(_) => Some(vec![Command::SetStatus(not_yet("creating a column"))]),
|
CellId::Create(_) => Some(vec![Command::SetStatus(not_yet("creating a column"))]),
|
||||||
CellId::NonItem { category, item } => Some(vec![Command::SetStatus(
|
CellId::NonItem { category, item } => Some(vec![Command::SetStatus(
|
||||||
crate::non_item_refusal(category, item),
|
crate::non_item_refusal(state.categories.category_display(category), item),
|
||||||
)]),
|
)]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1399,8 +1408,11 @@ fn reopened_at(
|
|||||||
pub(crate) fn declined_at(state: &ClientState, row: usize, col: usize) -> Option<String> {
|
pub(crate) fn declined_at(state: &ClientState, row: usize, col: usize) -> Option<String> {
|
||||||
match projected_at(state, row, col)?.id.as_ref()? {
|
match projected_at(state, row, col)?.id.as_ref()? {
|
||||||
CellId::Aggregate(_) => Some(AGGREGATE_DECLINE.to_string()),
|
CellId::Aggregate(_) => Some(AGGREGATE_DECLINE.to_string()),
|
||||||
CellId::Derived { target } => Some(derived_decline(target)),
|
CellId::Derived { target } => Some(derived_decline(state, target)),
|
||||||
CellId::NonItem { category, item } => Some(crate::non_item_refusal(category, item)),
|
CellId::NonItem { category, item } => Some(crate::non_item_refusal(
|
||||||
|
state.categories.category_display(category),
|
||||||
|
item,
|
||||||
|
)),
|
||||||
CellId::Record(_) | CellId::Create(_) | CellId::Coordinate { .. } => None,
|
CellId::Record(_) | CellId::Create(_) | CellId::Coordinate { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1433,7 +1445,8 @@ const AGGREGATE_DECLINE: &str =
|
|||||||
"Cannot write to an aggregate — it combines several records, so there is no one cell to write";
|
"Cannot write to an aggregate — it combines several records, so there is no one cell to write";
|
||||||
|
|
||||||
/// What both ends say of a formula's result: the host's `derived_refusal`.
|
/// What both ends say of a formula's result: the host's `derived_refusal`.
|
||||||
fn derived_decline(target: &str) -> String {
|
fn derived_decline(state: &ClientState, target: &str) -> String {
|
||||||
|
let target = state.categories.item_display("_Measure", target);
|
||||||
format!("{target} is computed by a formula — edit the formula, not the cell")
|
format!("{target} is computed by a formula — edit the formula, not the cell")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1613,7 +1626,7 @@ fn filter_to_item_at(state: &ClientState, index: usize) -> Option<Vec<Command>>
|
|||||||
Some(CatTreeEntry::Item {
|
Some(CatTreeEntry::Item {
|
||||||
cat_name,
|
cat_name,
|
||||||
item_name,
|
item_name,
|
||||||
..
|
display,
|
||||||
}) => Some(vec![
|
}) => Some(vec![
|
||||||
model(
|
model(
|
||||||
state,
|
state,
|
||||||
@@ -1631,7 +1644,10 @@ fn filter_to_item_at(state: &ClientState, index: usize) -> Option<Vec<Command>>
|
|||||||
item: item_name.clone(),
|
item: item_name.clone(),
|
||||||
},
|
},
|
||||||
)?,
|
)?,
|
||||||
Command::SetStatus(format!("Filter: {cat_name} = {item_name}")),
|
Command::SetStatus(filter_status(
|
||||||
|
state.categories.category_display(&cat_name),
|
||||||
|
&display,
|
||||||
|
)),
|
||||||
]),
|
]),
|
||||||
Some(CatTreeEntry::Category { .. }) => Some(toggle_cat_expand_at(state, index)),
|
Some(CatTreeEntry::Category { .. }) => Some(toggle_cat_expand_at(state, index)),
|
||||||
None => Some(Vec::new()),
|
None => Some(Vec::new()),
|
||||||
@@ -1642,14 +1658,14 @@ fn filter_to_item_at(state: &ClientState, index: usize) -> Option<Vec<Command>>
|
|||||||
/// row deletes the item, and an empty tree says so.
|
/// row deletes the item, and an empty tree says so.
|
||||||
fn delete_at_cat_cursor(state: &ClientState) -> Option<Vec<Command>> {
|
fn delete_at_cat_cursor(state: &ClientState) -> Option<Vec<Command>> {
|
||||||
match cat_tree_entry(state) {
|
match cat_tree_entry(state) {
|
||||||
Some(CatTreeEntry::Category { name, .. }) => Some(vec![
|
Some(CatTreeEntry::Category { name, display, .. }) => Some(vec![
|
||||||
model(state, ModelCommand::RemoveCategory(name.clone()))?,
|
model(state, ModelCommand::RemoveCategory(name.clone()))?,
|
||||||
Command::SetStatus(format!("Deleted category '{name}'")),
|
Command::SetStatus(deleted_category_status(&display)),
|
||||||
]),
|
]),
|
||||||
Some(CatTreeEntry::Item {
|
Some(CatTreeEntry::Item {
|
||||||
cat_name,
|
cat_name,
|
||||||
item_name,
|
item_name,
|
||||||
..
|
display,
|
||||||
}) => {
|
}) => {
|
||||||
let mut commands = vec![
|
let mut commands = vec![
|
||||||
model(
|
model(
|
||||||
@@ -1659,7 +1675,10 @@ fn delete_at_cat_cursor(state: &ClientState) -> Option<Vec<Command>> {
|
|||||||
item: item_name.clone(),
|
item: item_name.clone(),
|
||||||
},
|
},
|
||||||
)?,
|
)?,
|
||||||
Command::SetStatus(format!("Deleted item '{item_name}' from '{cat_name}'")),
|
Command::SetStatus(deleted_item_status(
|
||||||
|
&display,
|
||||||
|
state.categories.category_display(&cat_name),
|
||||||
|
)),
|
||||||
];
|
];
|
||||||
commands.extend(retreat_from_deleted_item(state));
|
commands.extend(retreat_from_deleted_item(state));
|
||||||
Some(commands)
|
Some(commands)
|
||||||
@@ -1786,7 +1805,7 @@ fn tile_axis_at(state: &ClientState, index: usize, axis: Option<Axis>) -> Option
|
|||||||
};
|
};
|
||||||
Some(vec![
|
Some(vec![
|
||||||
model(state, cmd)?,
|
model(state, cmd)?,
|
||||||
Command::SetStatus(axis_change_status(&tile.category, new_axis)),
|
Command::SetStatus(axis_change_status(&tile.display, new_axis)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5014,3 +5033,137 @@ mod opened_editor_wire_tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod display_tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
AxisFrame, CategoryItem, CategoryRow, CategoryTreeUpdate, CellDisplay, GridExtent,
|
||||||
|
GridUpdate, KeyModifiers, Projection, Snapshot, TileEntry, ViewState,
|
||||||
|
};
|
||||||
|
|
||||||
|
const REGION: &str = "Sales territory of the whole company";
|
||||||
|
const NORTH: &str = "Northern reaches and the colder coasts";
|
||||||
|
const TOTAL: &str = "Grand total of everything counted";
|
||||||
|
|
||||||
|
fn ev(code: KeyCode) -> KeyEvent {
|
||||||
|
KeyEvent::new(code, KeyModifiers::NONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shown(name: &str, display: &str) -> CategoryItem {
|
||||||
|
CategoryItem {
|
||||||
|
name: name.into(),
|
||||||
|
display: display.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every key differs from, and is far shorter than, what a person sees.
|
||||||
|
fn displayed(cell: Option<CellId>) -> ClientState {
|
||||||
|
let mut state = ClientState::default();
|
||||||
|
let mut first = CellDisplay::number("1");
|
||||||
|
first.id = cell;
|
||||||
|
state.bootstrap(Snapshot {
|
||||||
|
view: ViewState::default(),
|
||||||
|
seed: vec![
|
||||||
|
Projection::CategoryTree(CategoryTreeUpdate::Categories(vec![
|
||||||
|
CategoryRow {
|
||||||
|
name: "Region".into(),
|
||||||
|
display: REGION.into(),
|
||||||
|
items: vec![shown("North", NORTH)],
|
||||||
|
axis: Axis::Row,
|
||||||
|
},
|
||||||
|
CategoryRow {
|
||||||
|
name: "_Measure".into(),
|
||||||
|
display: "_Measure".into(),
|
||||||
|
items: vec![shown("Total", TOTAL)],
|
||||||
|
axis: Axis::Column,
|
||||||
|
},
|
||||||
|
])),
|
||||||
|
Projection::TileBar(vec![TileEntry {
|
||||||
|
category: "Region".into(),
|
||||||
|
display: REGION.into(),
|
||||||
|
axis: Axis::Row,
|
||||||
|
kind: crate::CategoryKind::Regular,
|
||||||
|
}]),
|
||||||
|
Projection::Grid(GridUpdate::RowFrame(AxisFrame::flat(["North"]))),
|
||||||
|
Projection::Grid(GridUpdate::ColFrame(AxisFrame::flat(["Total"]))),
|
||||||
|
Projection::Grid(GridUpdate::Cells(vec![((0, 0), first)])),
|
||||||
|
Projection::Grid(GridUpdate::Extent(GridExtent { rows: 1, cols: 1 })),
|
||||||
|
Projection::Basis(crate::basis::published(7, 3, Default::default())),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
state.window = (1, 1);
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_status(commands: Vec<Command>) -> String {
|
||||||
|
match commands.last() {
|
||||||
|
Some(Command::SetStatus(said)) => said.clone(),
|
||||||
|
other => panic!("no status last in {commands:?}: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local(state: &ClientState, code: KeyCode) -> Vec<Command> {
|
||||||
|
match resolve(state, ev(code)) {
|
||||||
|
KeyOutcome::Local(commands) => commands,
|
||||||
|
other => panic!("{code:?} did not resolve locally: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_panel_status_names_what_a_person_sees_not_the_keys() {
|
||||||
|
let mut state = displayed(None);
|
||||||
|
state.view.mode = AppMode::CategoryPanel;
|
||||||
|
state.view.expanded_cats.insert("Region".into());
|
||||||
|
state.view.cat_panel_cursor = 0;
|
||||||
|
let cycled = last_status(local(&state, KeyCode::Char(' ')));
|
||||||
|
assert_eq!(cycled, format!("{REGION} → Col"));
|
||||||
|
let deleted = last_status(delete_at_cat_cursor(&state).unwrap());
|
||||||
|
assert_eq!(deleted, format!("Deleted category '{REGION}'"));
|
||||||
|
|
||||||
|
state.view.cat_panel_cursor = 1;
|
||||||
|
let filtered = last_status(filter_to_item(&state).unwrap());
|
||||||
|
assert_eq!(filtered, format!("Filter: {REGION} = {NORTH}"));
|
||||||
|
let deleted = last_status(delete_at_cat_cursor(&state).unwrap());
|
||||||
|
assert_eq!(deleted, format!("Deleted item '{NORTH}' from '{REGION}'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_tile_status_names_the_display() {
|
||||||
|
let mut state = displayed(None);
|
||||||
|
state.view.mode = AppMode::TileSelect;
|
||||||
|
let said = last_status(tile_axis(&state, None).unwrap());
|
||||||
|
assert_eq!(said, format!("{REGION} → Col"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_item_prompt_names_the_display_and_the_mode_keeps_the_key() {
|
||||||
|
let mut state = displayed(None);
|
||||||
|
state.view.mode = AppMode::CategoryPanel;
|
||||||
|
state.view.cat_panel_cursor = 0;
|
||||||
|
let commands = local(&state, KeyCode::Char('a'));
|
||||||
|
let Some(Command::ChangeMode(AppMode::ItemAdd { category, minibuf })) = commands
|
||||||
|
.iter()
|
||||||
|
.find(|c| matches!(c, Command::ChangeMode(_)))
|
||||||
|
else {
|
||||||
|
panic!("no item-add mode in {commands:?}");
|
||||||
|
};
|
||||||
|
assert_eq!(category, "Region", "the commit adds to the KEY");
|
||||||
|
assert_eq!(minibuf.prompt, format!("add item to {REGION}: "));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_refused_cell_names_the_displays_it_shows() {
|
||||||
|
let derived = displayed(Some(CellId::Derived {
|
||||||
|
target: "Total".into(),
|
||||||
|
}));
|
||||||
|
let said = last_status(local(&derived, KeyCode::Char('i')));
|
||||||
|
assert!(said.starts_with(TOTAL), "{said}");
|
||||||
|
let stray = displayed(Some(CellId::NonItem {
|
||||||
|
category: "Region".into(),
|
||||||
|
item: "Mars".into(),
|
||||||
|
}));
|
||||||
|
let said = last_status(local(&stray, KeyCode::Char('i')));
|
||||||
|
assert!(said.contains(REGION) && said.starts_with("Mars "), "{said}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,11 +38,27 @@ impl CatTreeEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// What a moved axis says on the status line. One home: the tile bar and the
|
/// What a moved axis says on the status line. One home: the tile bar and the
|
||||||
/// category panel are two routes to one operation — improvise-25bf.
|
/// category panel are two routes to one operation — improvise-25bf. Every
|
||||||
|
/// name here is a display: the status is read by a person, never matched on.
|
||||||
pub fn axis_change_status(category: &str, axis: Axis) -> String {
|
pub fn axis_change_status(category: &str, axis: Axis) -> String {
|
||||||
format!("{category} → {}", axis.plain_label())
|
format!("{category} → {}", axis.plain_label())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What `filter-to-item` says; both ends, so one wording (improvise-cq1q.13).
|
||||||
|
pub fn filter_status(category: &str, item: &str) -> String {
|
||||||
|
format!("Filter: {category} = {item}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What deleting a category at the panel cursor says.
|
||||||
|
pub fn deleted_category_status(category: &str) -> String {
|
||||||
|
format!("Deleted category '{category}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What deleting an item at the panel cursor says.
|
||||||
|
pub fn deleted_item_status(item: &str, category: &str) -> String {
|
||||||
|
format!("Deleted item '{item}' from '{category}'")
|
||||||
|
}
|
||||||
|
|
||||||
/// Flatten the category panel: a header per category, then its items when
|
/// Flatten the category panel: a header per category, then its items when
|
||||||
/// expanded. The result index is `ViewState::cat_panel_cursor`.
|
/// expanded. The result index is `ViewState::cat_panel_cursor`.
|
||||||
pub fn flatten_cat_tree(
|
pub fn flatten_cat_tree(
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ pub fn render_model(state: &ClientState) -> RenderModel {
|
|||||||
mode: improvise_protocol::mode_label(&state.view.mode).to_string(),
|
mode: improvise_protocol::mode_label(&state.view.mode).to_string(),
|
||||||
status: state.view.status_msg.clone(),
|
status: state.view.status_msg.clone(),
|
||||||
status_stands: state.status_stands(),
|
status_stands: state.status_stands(),
|
||||||
view_name: state.view.active_view.clone(),
|
view_name: state.views.display_of(&state.view.active_view).to_string(),
|
||||||
doc_name: state.document.name.clone(),
|
doc_name: state.document.name.clone(),
|
||||||
doc_dirty: state.document.dirty,
|
doc_dirty: state.document.dirty,
|
||||||
doc_stored_as: state.document.stored_as.clone(),
|
doc_stored_as: state.document.stored_as.clone(),
|
||||||
@@ -883,6 +883,24 @@ mod tests {
|
|||||||
assert!(views[0].active, "the active view is still found by its key");
|
assert!(views[0].active, "the active view is still found by its key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_title_names_the_active_views_display_not_its_key() {
|
||||||
|
let mut client = init();
|
||||||
|
let state = client.state_mut();
|
||||||
|
state.fold(&Command::Project(Projection::ViewList(vec![ViewSummary {
|
||||||
|
name: "Default".into(),
|
||||||
|
display: "Quarterly overview of regional sales".into(),
|
||||||
|
axes: vec![],
|
||||||
|
}])));
|
||||||
|
state.view.active_view = "Default".into();
|
||||||
|
assert_eq!(
|
||||||
|
client.model().view_name,
|
||||||
|
"Quarterly overview of regional sales"
|
||||||
|
);
|
||||||
|
client.state_mut().view.active_view = "Unlisted".into();
|
||||||
|
assert_eq!(client.model().view_name, "Unlisted", "no display: the key");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_grid_page_bar_and_item_rows_paint_displays() {
|
fn the_grid_page_bar_and_item_rows_paint_displays() {
|
||||||
use improvise_protocol::{AxisFrame, CategoryItem, FrameGroup};
|
use improvise_protocol::{AxisFrame, CategoryItem, FrameGroup};
|
||||||
|
|||||||
+25
-1
@@ -409,7 +409,7 @@ fn draw_content(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
GridWidget::new(
|
GridWidget::new(
|
||||||
&grid_cache,
|
&grid_cache,
|
||||||
app.cursor(),
|
app.cursor(),
|
||||||
&app.view_state.active_view,
|
app.view().display(),
|
||||||
&app.layout,
|
&app.layout,
|
||||||
&app.view_state.mode,
|
&app.view_state.mode,
|
||||||
&app.view_state.search_query,
|
&app.view_state.search_query,
|
||||||
@@ -1201,6 +1201,30 @@ mod content_tests {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// improvise-cq1q.13: evidence in `context/test-evidence/core.md`.
|
||||||
|
#[test]
|
||||||
|
fn the_grid_title_names_the_views_display_not_its_key() {
|
||||||
|
let mut wb = wide_grid();
|
||||||
|
wb.active_view_mut()
|
||||||
|
.set_display("Quarterly overview of regional sales");
|
||||||
|
let mut app = App::new(wb, None);
|
||||||
|
(app.term_width, app.term_height) = (100, 24);
|
||||||
|
let mut terminal = Terminal::new(TestBackend::new(100, 24)).expect("test backend");
|
||||||
|
terminal.draw(|f| draw(f, &app)).expect("draw");
|
||||||
|
let text: String = terminal
|
||||||
|
.backend()
|
||||||
|
.buffer()
|
||||||
|
.content()
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.symbol())
|
||||||
|
.collect();
|
||||||
|
assert!(
|
||||||
|
text.contains("View: Quarterly overview of regional sales"),
|
||||||
|
"the title drew something else"
|
||||||
|
);
|
||||||
|
assert!(!text.contains("View: Default"), "the key was drawn");
|
||||||
|
}
|
||||||
|
|
||||||
/// improvise-gtn7.1: the cursor went behind a side panel —
|
/// improvise-gtn7.1: the cursor went behind a side panel —
|
||||||
/// `context/test-evidence/tui.md`.
|
/// `context/test-evidence/tui.md`.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+5
-4
@@ -23,7 +23,8 @@ pub struct GridWidget<'a> {
|
|||||||
/// This client's cursor in the active view (improvise-8uz). Passed in
|
/// This client's cursor in the active view (improvise-8uz). Passed in
|
||||||
/// rather than read off `View`, which no longer carries per-client state.
|
/// rather than read off `View`, which no longer carries per-client state.
|
||||||
pub cursor: ViewCursor,
|
pub cursor: ViewCursor,
|
||||||
pub view_name: &'a str,
|
/// What the title shows: the view's display, never its key (improvise-cq1q.13).
|
||||||
|
pub view_display: &'a str,
|
||||||
pub layout: &'a GridLayout,
|
pub layout: &'a GridLayout,
|
||||||
pub mode: &'a AppMode,
|
pub mode: &'a AppMode,
|
||||||
pub search_query: &'a str,
|
pub search_query: &'a str,
|
||||||
@@ -36,7 +37,7 @@ impl<'a> GridWidget<'a> {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
cache: &'a GridCache,
|
cache: &'a GridCache,
|
||||||
cursor: ViewCursor,
|
cursor: ViewCursor,
|
||||||
view_name: &'a str,
|
view_display: &'a str,
|
||||||
layout: &'a GridLayout,
|
layout: &'a GridLayout,
|
||||||
mode: &'a AppMode,
|
mode: &'a AppMode,
|
||||||
search_query: &'a str,
|
search_query: &'a str,
|
||||||
@@ -46,7 +47,7 @@ impl<'a> GridWidget<'a> {
|
|||||||
Self {
|
Self {
|
||||||
cache,
|
cache,
|
||||||
cursor,
|
cursor,
|
||||||
view_name,
|
view_display,
|
||||||
layout,
|
layout,
|
||||||
mode,
|
mode,
|
||||||
search_query,
|
search_query,
|
||||||
@@ -444,7 +445,7 @@ impl<'a> Widget for GridWidget<'a> {
|
|||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title(format!(" View: {} ", self.view_name));
|
.title(format!(" View: {} ", self.view_display));
|
||||||
let inner = block.inner(area);
|
let inner = block.inner(area);
|
||||||
block.render(area, buf);
|
block.render(area, buf);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user