chore: clippy + format
This commit is contained in:
@ -80,12 +80,7 @@ impl CmdRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Register a command with both a text parser and an interactive constructor.
|
/// Register a command with both a text parser and an interactive constructor.
|
||||||
pub fn register(
|
pub fn register(&mut self, name: &'static str, parse: ParseFn, interactive: InteractiveFn) {
|
||||||
&mut self,
|
|
||||||
name: &'static str,
|
|
||||||
parse: ParseFn,
|
|
||||||
interactive: InteractiveFn,
|
|
||||||
) {
|
|
||||||
self.entries.push(CmdEntry {
|
self.entries.push(CmdEntry {
|
||||||
name,
|
name,
|
||||||
parse: Box::new(parse),
|
parse: Box::new(parse),
|
||||||
@ -98,7 +93,9 @@ impl CmdRegistry {
|
|||||||
self.entries.push(CmdEntry {
|
self.entries.push(CmdEntry {
|
||||||
name,
|
name,
|
||||||
parse: Box::new(parse),
|
parse: Box::new(parse),
|
||||||
interactive: Box::new(|_args, _ctx| Err("not available interactively without args".into())),
|
interactive: Box::new(|_args, _ctx| {
|
||||||
|
Err("not available interactively without args".into())
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,8 +271,7 @@ impl Cmd for JumpToLastRow {
|
|||||||
}
|
}
|
||||||
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
||||||
let last = self.row_count.saturating_sub(1);
|
let last = self.row_count.saturating_sub(1);
|
||||||
let mut effects: Vec<Box<dyn Effect>> =
|
let mut effects: Vec<Box<dyn Effect>> = vec![Box::new(effect::SetSelected(last, self.col))];
|
||||||
vec![Box::new(effect::SetSelected(last, self.col))];
|
|
||||||
if last >= self.row_offset + 20 {
|
if last >= self.row_offset + 20 {
|
||||||
effects.push(Box::new(effect::SetRowOffset(last.saturating_sub(19))));
|
effects.push(Box::new(effect::SetRowOffset(last.saturating_sub(19))));
|
||||||
}
|
}
|
||||||
@ -311,8 +307,7 @@ impl Cmd for JumpToLastCol {
|
|||||||
}
|
}
|
||||||
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
||||||
let last = self.col_count.saturating_sub(1);
|
let last = self.col_count.saturating_sub(1);
|
||||||
let mut effects: Vec<Box<dyn Effect>> =
|
let mut effects: Vec<Box<dyn Effect>> = vec![Box::new(effect::SetSelected(self.row, last))];
|
||||||
vec![Box::new(effect::SetSelected(self.row, last))];
|
|
||||||
if last >= self.col_offset + 8 {
|
if last >= self.col_offset + 8 {
|
||||||
effects.push(Box::new(effect::SetColOffset(last.saturating_sub(7))));
|
effects.push(Box::new(effect::SetColOffset(last.saturating_sub(7))));
|
||||||
}
|
}
|
||||||
@ -1488,7 +1483,9 @@ impl Cmd for CommitCellEdit {
|
|||||||
effects.push(effect::mark_dirty());
|
effects.push(effect::mark_dirty());
|
||||||
effects.push(effect::change_mode(AppMode::Normal));
|
effects.push(effect::change_mode(AppMode::Normal));
|
||||||
// Advance cursor down (typewriter-style)
|
// Advance cursor down (typewriter-style)
|
||||||
let adv = EnterAdvance { cursor: CursorState::from_ctx(ctx) };
|
let adv = EnterAdvance {
|
||||||
|
cursor: CursorState::from_ctx(ctx),
|
||||||
|
};
|
||||||
effects.extend(adv.execute(ctx));
|
effects.extend(adv.execute(ctx));
|
||||||
effects
|
effects
|
||||||
}
|
}
|
||||||
@ -1879,9 +1876,7 @@ effect_cmd!(
|
|||||||
"save-as",
|
"save-as",
|
||||||
|args: &[String]| require_args("save-as", args, 1),
|
|args: &[String]| require_args("save-as", args, 1),
|
||||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||||
vec![Box::new(effect::SaveAs(std::path::PathBuf::from(
|
vec![Box::new(effect::SaveAs(std::path::PathBuf::from(&args[0])))]
|
||||||
&args[0],
|
|
||||||
)))]
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1973,52 +1968,127 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
require_args("move-selection", args, 2)?;
|
require_args("move-selection", args, 2)?;
|
||||||
let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
let dc = args[1].parse::<i32>().map_err(|e| e.to_string())?;
|
let dc = args[1].parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
Ok(Box::new(MoveSelection { dr, dc, cursor: CursorState { row: 0, col: 0, row_count: 0, col_count: 0, row_offset: 0, col_offset: 0 } }))
|
Ok(Box::new(MoveSelection {
|
||||||
|
dr,
|
||||||
|
dc,
|
||||||
|
cursor: CursorState {
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
row_count: 0,
|
||||||
|
col_count: 0,
|
||||||
|
row_offset: 0,
|
||||||
|
col_offset: 0,
|
||||||
|
},
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
|args, ctx| {
|
|args, ctx| {
|
||||||
require_args("move-selection", args, 2)?;
|
require_args("move-selection", args, 2)?;
|
||||||
let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
let dc = args[1].parse::<i32>().map_err(|e| e.to_string())?;
|
let dc = args[1].parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
Ok(Box::new(MoveSelection { dr, dc, cursor: CursorState::from_ctx(ctx) }))
|
Ok(Box::new(MoveSelection {
|
||||||
|
dr,
|
||||||
|
dc,
|
||||||
|
cursor: CursorState::from_ctx(ctx),
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"jump-first-row",
|
"jump-first-row",
|
||||||
|_| Ok(Box::new(JumpToFirstRow { col: 0 })),
|
|_| Ok(Box::new(JumpToFirstRow { col: 0 })),
|
||||||
|_, ctx| Ok(Box::new(JumpToFirstRow { col: ctx.selected.1 })),
|
|_, ctx| {
|
||||||
|
Ok(Box::new(JumpToFirstRow {
|
||||||
|
col: ctx.selected.1,
|
||||||
|
}))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"jump-last-row",
|
"jump-last-row",
|
||||||
|_| Ok(Box::new(JumpToLastRow { col: 0, row_count: 0, row_offset: 0 })),
|
|_| {
|
||||||
|_, ctx| Ok(Box::new(JumpToLastRow { col: ctx.selected.1, row_count: ctx.row_count, row_offset: ctx.row_offset })),
|
Ok(Box::new(JumpToLastRow {
|
||||||
|
col: 0,
|
||||||
|
row_count: 0,
|
||||||
|
row_offset: 0,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|_, ctx| {
|
||||||
|
Ok(Box::new(JumpToLastRow {
|
||||||
|
col: ctx.selected.1,
|
||||||
|
row_count: ctx.row_count,
|
||||||
|
row_offset: ctx.row_offset,
|
||||||
|
}))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"jump-first-col",
|
"jump-first-col",
|
||||||
|_| Ok(Box::new(JumpToFirstCol { row: 0 })),
|
|_| Ok(Box::new(JumpToFirstCol { row: 0 })),
|
||||||
|_, ctx| Ok(Box::new(JumpToFirstCol { row: ctx.selected.0 })),
|
|_, ctx| {
|
||||||
|
Ok(Box::new(JumpToFirstCol {
|
||||||
|
row: ctx.selected.0,
|
||||||
|
}))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"jump-last-col",
|
"jump-last-col",
|
||||||
|_| Ok(Box::new(JumpToLastCol { row: 0, col_count: 0, col_offset: 0 })),
|
|_| {
|
||||||
|_, ctx| Ok(Box::new(JumpToLastCol { row: ctx.selected.0, col_count: ctx.col_count, col_offset: ctx.col_offset })),
|
Ok(Box::new(JumpToLastCol {
|
||||||
|
row: 0,
|
||||||
|
col_count: 0,
|
||||||
|
col_offset: 0,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|_, ctx| {
|
||||||
|
Ok(Box::new(JumpToLastCol {
|
||||||
|
row: ctx.selected.0,
|
||||||
|
col_count: ctx.col_count,
|
||||||
|
col_offset: ctx.col_offset,
|
||||||
|
}))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"scroll-rows",
|
"scroll-rows",
|
||||||
|args| {
|
|args| {
|
||||||
require_args("scroll-rows", args, 1)?;
|
require_args("scroll-rows", args, 1)?;
|
||||||
let n = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
let n = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
Ok(Box::new(ScrollRows { delta: n, cursor: CursorState { row: 0, col: 0, row_count: 0, col_count: 0, row_offset: 0, col_offset: 0 } }))
|
Ok(Box::new(ScrollRows {
|
||||||
|
delta: n,
|
||||||
|
cursor: CursorState {
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
row_count: 0,
|
||||||
|
col_count: 0,
|
||||||
|
row_offset: 0,
|
||||||
|
col_offset: 0,
|
||||||
|
},
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
|args, ctx| {
|
|args, ctx| {
|
||||||
require_args("scroll-rows", args, 1)?;
|
require_args("scroll-rows", args, 1)?;
|
||||||
let n = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
let n = args[0].parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
Ok(Box::new(ScrollRows { delta: n, cursor: CursorState::from_ctx(ctx) }))
|
Ok(Box::new(ScrollRows {
|
||||||
|
delta: n,
|
||||||
|
cursor: CursorState::from_ctx(ctx),
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"enter-advance",
|
"enter-advance",
|
||||||
|_| Ok(Box::new(EnterAdvance { cursor: CursorState { row: 0, col: 0, row_count: 0, col_count: 0, row_offset: 0, col_offset: 0 } })),
|
|_| {
|
||||||
|_, ctx| Ok(Box::new(EnterAdvance { cursor: CursorState::from_ctx(ctx) })),
|
Ok(Box::new(EnterAdvance {
|
||||||
|
cursor: CursorState {
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
row_count: 0,
|
||||||
|
col_count: 0,
|
||||||
|
row_offset: 0,
|
||||||
|
col_offset: 0,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|_, ctx| {
|
||||||
|
Ok(Box::new(EnterAdvance {
|
||||||
|
cursor: CursorState::from_ctx(ctx),
|
||||||
|
}))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── Cell operations ──────────────────────────────────────────────────
|
// ── Cell operations ──────────────────────────────────────────────────
|
||||||
@ -2094,11 +2164,21 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
"category-panel" => AppMode::CategoryPanel,
|
"category-panel" => AppMode::CategoryPanel,
|
||||||
"view-panel" => AppMode::ViewPanel,
|
"view-panel" => AppMode::ViewPanel,
|
||||||
"tile-select" => AppMode::TileSelect,
|
"tile-select" => AppMode::TileSelect,
|
||||||
"command" => AppMode::CommandMode { buffer: String::new() },
|
"command" => AppMode::CommandMode {
|
||||||
"category-add" => AppMode::CategoryAdd { buffer: String::new() },
|
buffer: String::new(),
|
||||||
"editing" => AppMode::Editing { buffer: String::new() },
|
},
|
||||||
"formula-edit" => AppMode::FormulaEdit { buffer: String::new() },
|
"category-add" => AppMode::CategoryAdd {
|
||||||
"export-prompt" => AppMode::ExportPrompt { buffer: String::new() },
|
buffer: String::new(),
|
||||||
|
},
|
||||||
|
"editing" => AppMode::Editing {
|
||||||
|
buffer: String::new(),
|
||||||
|
},
|
||||||
|
"formula-edit" => AppMode::FormulaEdit {
|
||||||
|
buffer: String::new(),
|
||||||
|
},
|
||||||
|
"export-prompt" => AppMode::ExportPrompt {
|
||||||
|
buffer: String::new(),
|
||||||
|
},
|
||||||
other => return Err(format!("Unknown mode: {other}")),
|
other => return Err(format!("Unknown mode: {other}")),
|
||||||
};
|
};
|
||||||
Ok(Box::new(EnterMode(mode)))
|
Ok(Box::new(EnterMode(mode)))
|
||||||
@ -2120,7 +2200,10 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
|args| {
|
|args| {
|
||||||
require_args("toggle-panel-and-focus", args, 1)?;
|
require_args("toggle-panel-and-focus", args, 1)?;
|
||||||
let panel = parse_panel(&args[0])?;
|
let panel = parse_panel(&args[0])?;
|
||||||
Ok(Box::new(TogglePanelAndFocus { panel, currently_open: false }))
|
Ok(Box::new(TogglePanelAndFocus {
|
||||||
|
panel,
|
||||||
|
currently_open: false,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
|args, ctx| {
|
|args, ctx| {
|
||||||
require_args("toggle-panel-and-focus", args, 1)?;
|
require_args("toggle-panel-and-focus", args, 1)?;
|
||||||
@ -2130,7 +2213,10 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
Panel::Category => ctx.category_panel_open,
|
Panel::Category => ctx.category_panel_open,
|
||||||
Panel::View => ctx.view_panel_open,
|
Panel::View => ctx.view_panel_open,
|
||||||
};
|
};
|
||||||
Ok(Box::new(TogglePanelAndFocus { panel, currently_open }))
|
Ok(Box::new(TogglePanelAndFocus {
|
||||||
|
panel,
|
||||||
|
currently_open,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
@ -2138,7 +2224,10 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
|args| {
|
|args| {
|
||||||
require_args("toggle-panel-visibility", args, 1)?;
|
require_args("toggle-panel-visibility", args, 1)?;
|
||||||
let panel = parse_panel(&args[0])?;
|
let panel = parse_panel(&args[0])?;
|
||||||
Ok(Box::new(TogglePanelVisibility { panel, currently_open: false }))
|
Ok(Box::new(TogglePanelVisibility {
|
||||||
|
panel,
|
||||||
|
currently_open: false,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
|args, ctx| {
|
|args, ctx| {
|
||||||
require_args("toggle-panel-visibility", args, 1)?;
|
require_args("toggle-panel-visibility", args, 1)?;
|
||||||
@ -2148,17 +2237,28 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
Panel::Category => ctx.category_panel_open,
|
Panel::Category => ctx.category_panel_open,
|
||||||
Panel::View => ctx.view_panel_open,
|
Panel::View => ctx.view_panel_open,
|
||||||
};
|
};
|
||||||
Ok(Box::new(TogglePanelVisibility { panel, currently_open }))
|
Ok(Box::new(TogglePanelVisibility {
|
||||||
|
panel,
|
||||||
|
currently_open,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"cycle-panel-focus",
|
"cycle-panel-focus",
|
||||||
|_| Ok(Box::new(CyclePanelFocus { formula_open: false, category_open: false, view_open: false })),
|
|_| {
|
||||||
|_, ctx| Ok(Box::new(CyclePanelFocus {
|
Ok(Box::new(CyclePanelFocus {
|
||||||
formula_open: ctx.formula_panel_open,
|
formula_open: false,
|
||||||
category_open: ctx.category_panel_open,
|
category_open: false,
|
||||||
view_open: ctx.view_panel_open,
|
view_open: false,
|
||||||
})),
|
}))
|
||||||
|
},
|
||||||
|
|_, ctx| {
|
||||||
|
Ok(Box::new(CyclePanelFocus {
|
||||||
|
formula_open: ctx.formula_panel_open,
|
||||||
|
category_open: ctx.category_panel_open,
|
||||||
|
view_open: ctx.view_panel_open,
|
||||||
|
}))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
r.register(
|
r.register(
|
||||||
"move-panel-cursor",
|
"move-panel-cursor",
|
||||||
@ -2166,7 +2266,12 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
require_args("move-panel-cursor", args, 2)?;
|
require_args("move-panel-cursor", args, 2)?;
|
||||||
let panel = parse_panel(&args[0])?;
|
let panel = parse_panel(&args[0])?;
|
||||||
let delta = args[1].parse::<i32>().map_err(|e| e.to_string())?;
|
let delta = args[1].parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
Ok(Box::new(MovePanelCursor { panel, delta, current: 0, max: 0 }))
|
Ok(Box::new(MovePanelCursor {
|
||||||
|
panel,
|
||||||
|
delta,
|
||||||
|
current: 0,
|
||||||
|
max: 0,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
|args, ctx| {
|
|args, ctx| {
|
||||||
require_args("move-panel-cursor", args, 2)?;
|
require_args("move-panel-cursor", args, 2)?;
|
||||||
@ -2177,10 +2282,17 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
Panel::Category => (ctx.cat_panel_cursor, ctx.model.category_names().len()),
|
Panel::Category => (ctx.cat_panel_cursor, ctx.model.category_names().len()),
|
||||||
Panel::View => (ctx.view_panel_cursor, ctx.model.views.len()),
|
Panel::View => (ctx.view_panel_cursor, ctx.model.views.len()),
|
||||||
};
|
};
|
||||||
Ok(Box::new(MovePanelCursor { panel, delta, current, max }))
|
Ok(Box::new(MovePanelCursor {
|
||||||
|
panel,
|
||||||
|
delta,
|
||||||
|
current,
|
||||||
|
max,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
r.register_nullary("delete-formula-at-cursor", || Box::new(DeleteFormulaAtCursor));
|
r.register_nullary("delete-formula-at-cursor", || {
|
||||||
|
Box::new(DeleteFormulaAtCursor)
|
||||||
|
});
|
||||||
r.register_nullary("cycle-axis-at-cursor", || Box::new(CycleAxisAtCursor));
|
r.register_nullary("cycle-axis-at-cursor", || Box::new(CycleAxisAtCursor));
|
||||||
r.register_nullary("open-item-add-at-cursor", || Box::new(OpenItemAddAtCursor));
|
r.register_nullary("open-item-add-at-cursor", || Box::new(OpenItemAddAtCursor));
|
||||||
r.register_nullary("switch-view-at-cursor", || Box::new(SwitchViewAtCursor));
|
r.register_nullary("switch-view-at-cursor", || Box::new(SwitchViewAtCursor));
|
||||||
@ -2201,18 +2313,26 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Grid operations ──────────────────────────────────────────────────
|
// ── Grid operations ──────────────────────────────────────────────────
|
||||||
r.register_nullary("toggle-group-under-cursor", || Box::new(ToggleGroupUnderCursor));
|
r.register_nullary("toggle-group-under-cursor", || {
|
||||||
r.register_nullary("toggle-col-group-under-cursor", || Box::new(ToggleColGroupUnderCursor));
|
Box::new(ToggleGroupUnderCursor)
|
||||||
|
});
|
||||||
|
r.register_nullary("toggle-col-group-under-cursor", || {
|
||||||
|
Box::new(ToggleColGroupUnderCursor)
|
||||||
|
});
|
||||||
r.register_nullary("hide-selected-row-item", || Box::new(HideSelectedRowItem));
|
r.register_nullary("hide-selected-row-item", || Box::new(HideSelectedRowItem));
|
||||||
|
|
||||||
// ── Text buffer ──────────────────────────────────────────────────────
|
// ── Text buffer ──────────────────────────────────────────────────────
|
||||||
r.register_pure("append-char", |args| {
|
r.register_pure("append-char", |args| {
|
||||||
require_args("append-char", args, 1)?;
|
require_args("append-char", args, 1)?;
|
||||||
Ok(Box::new(AppendChar { buffer: args[0].clone() }))
|
Ok(Box::new(AppendChar {
|
||||||
|
buffer: args[0].clone(),
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
r.register_pure("pop-char", |args| {
|
r.register_pure("pop-char", |args| {
|
||||||
require_args("pop-char", args, 1)?;
|
require_args("pop-char", args, 1)?;
|
||||||
Ok(Box::new(PopChar { buffer: args[0].clone() }))
|
Ok(Box::new(PopChar {
|
||||||
|
buffer: args[0].clone(),
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
r.register_nullary("command-mode-backspace", || Box::new(CommandModeBackspace));
|
r.register_nullary("command-mode-backspace", || Box::new(CommandModeBackspace));
|
||||||
|
|
||||||
@ -2321,7 +2441,11 @@ mod tests {
|
|||||||
fn move_selection_down_produces_set_selected() {
|
fn move_selection_down_produces_set_selected() {
|
||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let ctx = make_ctx(&m);
|
let ctx = make_ctx(&m);
|
||||||
let cmd = MoveSelection { dr: 1, dc: 0, cursor: CursorState::from_ctx(&ctx) };
|
let cmd = MoveSelection {
|
||||||
|
dr: 1,
|
||||||
|
dc: 0,
|
||||||
|
cursor: CursorState::from_ctx(&ctx),
|
||||||
|
};
|
||||||
let effects = cmd.execute(&ctx);
|
let effects = cmd.execute(&ctx);
|
||||||
// Should produce at least SetSelected
|
// Should produce at least SetSelected
|
||||||
assert!(!effects.is_empty());
|
assert!(!effects.is_empty());
|
||||||
@ -2332,7 +2456,11 @@ mod tests {
|
|||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let ctx = make_ctx(&m);
|
let ctx = make_ctx(&m);
|
||||||
// Try to move way past the end
|
// Try to move way past the end
|
||||||
let cmd = MoveSelection { dr: 100, dc: 100, cursor: CursorState::from_ctx(&ctx) };
|
let cmd = MoveSelection {
|
||||||
|
dr: 100,
|
||||||
|
dc: 100,
|
||||||
|
cursor: CursorState::from_ctx(&ctx),
|
||||||
|
};
|
||||||
let effects = cmd.execute(&ctx);
|
let effects = cmd.execute(&ctx);
|
||||||
assert!(!effects.is_empty());
|
assert!(!effects.is_empty());
|
||||||
}
|
}
|
||||||
@ -2403,7 +2531,10 @@ mod tests {
|
|||||||
fn toggle_panel_and_focus_opens_and_enters_mode() {
|
fn toggle_panel_and_focus_opens_and_enters_mode() {
|
||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let ctx = make_ctx(&m);
|
let ctx = make_ctx(&m);
|
||||||
let cmd = TogglePanelAndFocus { panel: effect::Panel::Formula, currently_open: false };
|
let cmd = TogglePanelAndFocus {
|
||||||
|
panel: effect::Panel::Formula,
|
||||||
|
currently_open: false,
|
||||||
|
};
|
||||||
let effects = cmd.execute(&ctx);
|
let effects = cmd.execute(&ctx);
|
||||||
assert_eq!(effects.len(), 2); // SetPanelOpen + ChangeMode
|
assert_eq!(effects.len(), 2); // SetPanelOpen + ChangeMode
|
||||||
let dbg = format!("{:?}", effects[1]);
|
let dbg = format!("{:?}", effects[1]);
|
||||||
@ -2418,7 +2549,10 @@ mod tests {
|
|||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let mut ctx = make_ctx(&m);
|
let mut ctx = make_ctx(&m);
|
||||||
ctx.formula_panel_open = true;
|
ctx.formula_panel_open = true;
|
||||||
let cmd = TogglePanelAndFocus { panel: effect::Panel::Formula, currently_open: true };
|
let cmd = TogglePanelAndFocus {
|
||||||
|
panel: effect::Panel::Formula,
|
||||||
|
currently_open: true,
|
||||||
|
};
|
||||||
let effects = cmd.execute(&ctx);
|
let effects = cmd.execute(&ctx);
|
||||||
assert_eq!(effects.len(), 1); // SetPanelOpen only, no mode change
|
assert_eq!(effects.len(), 1); // SetPanelOpen only, no mode change
|
||||||
}
|
}
|
||||||
@ -2427,7 +2561,9 @@ mod tests {
|
|||||||
fn enter_advance_moves_down() {
|
fn enter_advance_moves_down() {
|
||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let ctx = make_ctx(&m);
|
let ctx = make_ctx(&m);
|
||||||
let cmd = EnterAdvance { cursor: CursorState::from_ctx(&ctx) };
|
let cmd = EnterAdvance {
|
||||||
|
cursor: CursorState::from_ctx(&ctx),
|
||||||
|
};
|
||||||
let effects = cmd.execute(&ctx);
|
let effects = cmd.execute(&ctx);
|
||||||
assert!(!effects.is_empty());
|
assert!(!effects.is_empty());
|
||||||
let dbg = format!("{:?}", effects[0]);
|
let dbg = format!("{:?}", effects[0]);
|
||||||
@ -2510,7 +2646,11 @@ mod tests {
|
|||||||
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();
|
||||||
let ctx = make_ctx(&m);
|
let ctx = make_ctx(&m);
|
||||||
let cmd = CyclePanelFocus { formula_open: false, category_open: false, view_open: false };
|
let cmd = CyclePanelFocus {
|
||||||
|
formula_open: false,
|
||||||
|
category_open: false,
|
||||||
|
view_open: false,
|
||||||
|
};
|
||||||
let effects = cmd.execute(&ctx);
|
let effects = cmd.execute(&ctx);
|
||||||
assert!(effects.is_empty());
|
assert!(effects.is_empty());
|
||||||
}
|
}
|
||||||
@ -2520,7 +2660,11 @@ mod tests {
|
|||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let mut ctx = make_ctx(&m);
|
let mut ctx = make_ctx(&m);
|
||||||
ctx.formula_panel_open = true;
|
ctx.formula_panel_open = true;
|
||||||
let cmd = CyclePanelFocus { formula_open: true, category_open: false, view_open: false };
|
let cmd = CyclePanelFocus {
|
||||||
|
formula_open: true,
|
||||||
|
category_open: false,
|
||||||
|
view_open: false,
|
||||||
|
};
|
||||||
let effects = cmd.execute(&ctx);
|
let effects = cmd.execute(&ctx);
|
||||||
assert_eq!(effects.len(), 1);
|
assert_eq!(effects.len(), 1);
|
||||||
let dbg = format!("{:?}", effects[0]);
|
let dbg = format!("{:?}", effects[0]);
|
||||||
|
|||||||
@ -98,10 +98,7 @@ impl Keymap {
|
|||||||
pub fn bind(&mut self, key: KeyCode, mods: KeyModifiers, name: &'static str) {
|
pub fn bind(&mut self, key: KeyCode, mods: KeyModifiers, name: &'static str) {
|
||||||
self.bindings.insert(
|
self.bindings.insert(
|
||||||
KeyPattern::Key(key, mods),
|
KeyPattern::Key(key, mods),
|
||||||
Binding::Cmd {
|
Binding::Cmd { name, args: vec![] },
|
||||||
name,
|
|
||||||
args: vec![],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,13 +128,8 @@ impl Keymap {
|
|||||||
|
|
||||||
/// Bind a catch-all for any key at all.
|
/// Bind a catch-all for any key at all.
|
||||||
pub fn bind_any(&mut self, name: &'static str) {
|
pub fn bind_any(&mut self, name: &'static str) {
|
||||||
self.bindings.insert(
|
self.bindings
|
||||||
KeyPattern::Any,
|
.insert(KeyPattern::Any, Binding::Cmd { name, args: vec![] });
|
||||||
Binding::Cmd {
|
|
||||||
name,
|
|
||||||
args: vec![],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the binding for a key.
|
/// Look up the binding for a key.
|
||||||
@ -246,7 +238,12 @@ impl KeymapSet {
|
|||||||
(KeyCode::Left, 0, -1),
|
(KeyCode::Left, 0, -1),
|
||||||
(KeyCode::Right, 0, 1),
|
(KeyCode::Right, 0, 1),
|
||||||
] {
|
] {
|
||||||
normal.bind_args(key, none, "move-selection", vec![dr.to_string(), dc.to_string()]);
|
normal.bind_args(
|
||||||
|
key,
|
||||||
|
none,
|
||||||
|
"move-selection",
|
||||||
|
vec![dr.to_string(), dc.to_string()],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for (ch, dr, dc) in [('k', -1, 0), ('j', 1, 0), ('h', 0, -1), ('l', 0, 1)] {
|
for (ch, dr, dc) in [('k', -1, 0), ('j', 1, 0), ('h', 0, -1), ('l', 0, 1)] {
|
||||||
normal.bind_args(
|
normal.bind_args(
|
||||||
@ -333,7 +330,12 @@ impl KeymapSet {
|
|||||||
normal.bind(KeyCode::Char('e'), ctrl, "enter-export-prompt");
|
normal.bind(KeyCode::Char('e'), ctrl, "enter-export-prompt");
|
||||||
|
|
||||||
// Search / category add
|
// Search / category add
|
||||||
normal.bind_args(KeyCode::Char('n'), none, "search-navigate", vec!["forward".into()]);
|
normal.bind_args(
|
||||||
|
KeyCode::Char('n'),
|
||||||
|
none,
|
||||||
|
"search-navigate",
|
||||||
|
vec!["forward".into()],
|
||||||
|
);
|
||||||
normal.bind(KeyCode::Char('N'), shift, "search-or-category-add");
|
normal.bind(KeyCode::Char('N'), shift, "search-or-category-add");
|
||||||
|
|
||||||
// Page navigation
|
// Page navigation
|
||||||
@ -370,7 +372,12 @@ impl KeymapSet {
|
|||||||
// ── Help mode ────────────────────────────────────────────────────
|
// ── Help mode ────────────────────────────────────────────────────
|
||||||
let mut help = Keymap::new();
|
let mut help = Keymap::new();
|
||||||
help.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
help.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
||||||
help.bind_args(KeyCode::Char('q'), none, "enter-mode", vec!["normal".into()]);
|
help.bind_args(
|
||||||
|
KeyCode::Char('q'),
|
||||||
|
none,
|
||||||
|
"enter-mode",
|
||||||
|
vec!["normal".into()],
|
||||||
|
);
|
||||||
set.insert(ModeKey::Help, Arc::new(help));
|
set.insert(ModeKey::Help, Arc::new(help));
|
||||||
|
|
||||||
// ── Formula panel ────────────────────────────────────────────────
|
// ── Formula panel ────────────────────────────────────────────────
|
||||||
@ -378,10 +385,20 @@ impl KeymapSet {
|
|||||||
fp.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
fp.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
||||||
fp.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
fp.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
||||||
for key in [KeyCode::Up, KeyCode::Char('k')] {
|
for key in [KeyCode::Up, KeyCode::Char('k')] {
|
||||||
fp.bind_args(key, none, "move-panel-cursor", vec!["formula".into(), "-1".into()]);
|
fp.bind_args(
|
||||||
|
key,
|
||||||
|
none,
|
||||||
|
"move-panel-cursor",
|
||||||
|
vec!["formula".into(), "-1".into()],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for key in [KeyCode::Down, KeyCode::Char('j')] {
|
for key in [KeyCode::Down, KeyCode::Char('j')] {
|
||||||
fp.bind_args(key, none, "move-panel-cursor", vec!["formula".into(), "1".into()]);
|
fp.bind_args(
|
||||||
|
key,
|
||||||
|
none,
|
||||||
|
"move-panel-cursor",
|
||||||
|
vec!["formula".into(), "1".into()],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fp.bind(KeyCode::Char('a'), none, "enter-formula-edit");
|
fp.bind(KeyCode::Char('a'), none, "enter-formula-edit");
|
||||||
fp.bind(KeyCode::Char('n'), none, "enter-formula-edit");
|
fp.bind(KeyCode::Char('n'), none, "enter-formula-edit");
|
||||||
@ -395,10 +412,20 @@ impl KeymapSet {
|
|||||||
cp.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
cp.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
||||||
cp.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
cp.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
||||||
for key in [KeyCode::Up, KeyCode::Char('k')] {
|
for key in [KeyCode::Up, KeyCode::Char('k')] {
|
||||||
cp.bind_args(key, none, "move-panel-cursor", vec!["category".into(), "-1".into()]);
|
cp.bind_args(
|
||||||
|
key,
|
||||||
|
none,
|
||||||
|
"move-panel-cursor",
|
||||||
|
vec!["category".into(), "-1".into()],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for key in [KeyCode::Down, KeyCode::Char('j')] {
|
for key in [KeyCode::Down, KeyCode::Char('j')] {
|
||||||
cp.bind_args(key, none, "move-panel-cursor", vec!["category".into(), "1".into()]);
|
cp.bind_args(
|
||||||
|
key,
|
||||||
|
none,
|
||||||
|
"move-panel-cursor",
|
||||||
|
vec!["category".into(), "1".into()],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
cp.bind(KeyCode::Enter, none, "cycle-axis-at-cursor");
|
cp.bind(KeyCode::Enter, none, "cycle-axis-at-cursor");
|
||||||
cp.bind(KeyCode::Char(' '), none, "cycle-axis-at-cursor");
|
cp.bind(KeyCode::Char(' '), none, "cycle-axis-at-cursor");
|
||||||
@ -417,10 +444,20 @@ impl KeymapSet {
|
|||||||
vp.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
vp.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
||||||
vp.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
vp.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
||||||
for key in [KeyCode::Up, KeyCode::Char('k')] {
|
for key in [KeyCode::Up, KeyCode::Char('k')] {
|
||||||
vp.bind_args(key, none, "move-panel-cursor", vec!["view".into(), "-1".into()]);
|
vp.bind_args(
|
||||||
|
key,
|
||||||
|
none,
|
||||||
|
"move-panel-cursor",
|
||||||
|
vec!["view".into(), "-1".into()],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for key in [KeyCode::Down, KeyCode::Char('j')] {
|
for key in [KeyCode::Down, KeyCode::Char('j')] {
|
||||||
vp.bind_args(key, none, "move-panel-cursor", vec!["view".into(), "1".into()]);
|
vp.bind_args(
|
||||||
|
key,
|
||||||
|
none,
|
||||||
|
"move-panel-cursor",
|
||||||
|
vec!["view".into(), "1".into()],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
vp.bind(KeyCode::Enter, none, "switch-view-at-cursor");
|
vp.bind(KeyCode::Enter, none, "switch-view-at-cursor");
|
||||||
vp.bind(KeyCode::Char('n'), none, "create-and-switch-view");
|
vp.bind(KeyCode::Char('n'), none, "create-and-switch-view");
|
||||||
@ -434,15 +471,45 @@ impl KeymapSet {
|
|||||||
ts.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
ts.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
||||||
ts.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
ts.bind_args(KeyCode::Tab, none, "enter-mode", vec!["normal".into()]);
|
||||||
ts.bind_args(KeyCode::Left, none, "move-tile-cursor", vec!["-1".into()]);
|
ts.bind_args(KeyCode::Left, none, "move-tile-cursor", vec!["-1".into()]);
|
||||||
ts.bind_args(KeyCode::Char('h'), none, "move-tile-cursor", vec!["-1".into()]);
|
ts.bind_args(
|
||||||
|
KeyCode::Char('h'),
|
||||||
|
none,
|
||||||
|
"move-tile-cursor",
|
||||||
|
vec!["-1".into()],
|
||||||
|
);
|
||||||
ts.bind_args(KeyCode::Right, none, "move-tile-cursor", vec!["1".into()]);
|
ts.bind_args(KeyCode::Right, none, "move-tile-cursor", vec!["1".into()]);
|
||||||
ts.bind_args(KeyCode::Char('l'), none, "move-tile-cursor", vec!["1".into()]);
|
ts.bind_args(
|
||||||
|
KeyCode::Char('l'),
|
||||||
|
none,
|
||||||
|
"move-tile-cursor",
|
||||||
|
vec!["1".into()],
|
||||||
|
);
|
||||||
ts.bind(KeyCode::Enter, none, "cycle-axis-for-tile");
|
ts.bind(KeyCode::Enter, none, "cycle-axis-for-tile");
|
||||||
ts.bind(KeyCode::Char(' '), none, "cycle-axis-for-tile");
|
ts.bind(KeyCode::Char(' '), none, "cycle-axis-for-tile");
|
||||||
ts.bind_args(KeyCode::Char('r'), none, "set-axis-for-tile", vec!["row".into()]);
|
ts.bind_args(
|
||||||
ts.bind_args(KeyCode::Char('c'), none, "set-axis-for-tile", vec!["column".into()]);
|
KeyCode::Char('r'),
|
||||||
ts.bind_args(KeyCode::Char('p'), none, "set-axis-for-tile", vec!["page".into()]);
|
none,
|
||||||
ts.bind_args(KeyCode::Char('n'), none, "set-axis-for-tile", vec!["none".into()]);
|
"set-axis-for-tile",
|
||||||
|
vec!["row".into()],
|
||||||
|
);
|
||||||
|
ts.bind_args(
|
||||||
|
KeyCode::Char('c'),
|
||||||
|
none,
|
||||||
|
"set-axis-for-tile",
|
||||||
|
vec!["column".into()],
|
||||||
|
);
|
||||||
|
ts.bind_args(
|
||||||
|
KeyCode::Char('p'),
|
||||||
|
none,
|
||||||
|
"set-axis-for-tile",
|
||||||
|
vec!["page".into()],
|
||||||
|
);
|
||||||
|
ts.bind_args(
|
||||||
|
KeyCode::Char('n'),
|
||||||
|
none,
|
||||||
|
"set-axis-for-tile",
|
||||||
|
vec!["none".into()],
|
||||||
|
);
|
||||||
set.insert(ModeKey::TileSelect, Arc::new(ts));
|
set.insert(ModeKey::TileSelect, Arc::new(ts));
|
||||||
|
|
||||||
// ── Editing mode ─────────────────────────────────────────────────
|
// ── Editing mode ─────────────────────────────────────────────────
|
||||||
@ -455,7 +522,12 @@ impl KeymapSet {
|
|||||||
|
|
||||||
// ── Formula edit ─────────────────────────────────────────────────
|
// ── Formula edit ─────────────────────────────────────────────────
|
||||||
let mut fe = Keymap::new();
|
let mut fe = Keymap::new();
|
||||||
fe.bind_args(KeyCode::Esc, none, "enter-mode", vec!["formula-panel".into()]);
|
fe.bind_args(
|
||||||
|
KeyCode::Esc,
|
||||||
|
none,
|
||||||
|
"enter-mode",
|
||||||
|
vec!["formula-panel".into()],
|
||||||
|
);
|
||||||
fe.bind(KeyCode::Enter, none, "commit-formula");
|
fe.bind(KeyCode::Enter, none, "commit-formula");
|
||||||
fe.bind_args(KeyCode::Backspace, none, "pop-char", vec!["formula".into()]);
|
fe.bind_args(KeyCode::Backspace, none, "pop-char", vec!["formula".into()]);
|
||||||
fe.bind_any_char("append-char", vec!["formula".into()]);
|
fe.bind_any_char("append-char", vec!["formula".into()]);
|
||||||
@ -463,16 +535,31 @@ impl KeymapSet {
|
|||||||
|
|
||||||
// ── Category add ─────────────────────────────────────────────────
|
// ── Category add ─────────────────────────────────────────────────
|
||||||
let mut ca = Keymap::new();
|
let mut ca = Keymap::new();
|
||||||
ca.bind_args(KeyCode::Esc, none, "enter-mode", vec!["category-panel".into()]);
|
ca.bind_args(
|
||||||
|
KeyCode::Esc,
|
||||||
|
none,
|
||||||
|
"enter-mode",
|
||||||
|
vec!["category-panel".into()],
|
||||||
|
);
|
||||||
ca.bind(KeyCode::Enter, none, "commit-category-add");
|
ca.bind(KeyCode::Enter, none, "commit-category-add");
|
||||||
ca.bind(KeyCode::Tab, none, "commit-category-add");
|
ca.bind(KeyCode::Tab, none, "commit-category-add");
|
||||||
ca.bind_args(KeyCode::Backspace, none, "pop-char", vec!["category".into()]);
|
ca.bind_args(
|
||||||
|
KeyCode::Backspace,
|
||||||
|
none,
|
||||||
|
"pop-char",
|
||||||
|
vec!["category".into()],
|
||||||
|
);
|
||||||
ca.bind_any_char("append-char", vec!["category".into()]);
|
ca.bind_any_char("append-char", vec!["category".into()]);
|
||||||
set.insert(ModeKey::CategoryAdd, Arc::new(ca));
|
set.insert(ModeKey::CategoryAdd, Arc::new(ca));
|
||||||
|
|
||||||
// ── Item add ─────────────────────────────────────────────────────
|
// ── Item add ─────────────────────────────────────────────────────
|
||||||
let mut ia = Keymap::new();
|
let mut ia = Keymap::new();
|
||||||
ia.bind_args(KeyCode::Esc, none, "enter-mode", vec!["category-panel".into()]);
|
ia.bind_args(
|
||||||
|
KeyCode::Esc,
|
||||||
|
none,
|
||||||
|
"enter-mode",
|
||||||
|
vec!["category-panel".into()],
|
||||||
|
);
|
||||||
ia.bind(KeyCode::Enter, none, "commit-item-add");
|
ia.bind(KeyCode::Enter, none, "commit-item-add");
|
||||||
ia.bind(KeyCode::Tab, none, "commit-item-add");
|
ia.bind(KeyCode::Tab, none, "commit-item-add");
|
||||||
ia.bind_args(KeyCode::Backspace, none, "pop-char", vec!["item".into()]);
|
ia.bind_args(KeyCode::Backspace, none, "pop-char", vec!["item".into()]);
|
||||||
|
|||||||
@ -134,8 +134,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_quoted_string() {
|
fn parse_quoted_string() {
|
||||||
let cmds =
|
let cmds = parse_line(r#"add-formula Measure "Profit = Revenue - Cost""#).unwrap();
|
||||||
parse_line(r#"add-formula Measure "Profit = Revenue - Cost""#).unwrap();
|
|
||||||
assert_eq!(cmds.len(), 1);
|
assert_eq!(cmds.len(), 1);
|
||||||
assert_eq!(cmds[0].name(), "add-formula");
|
assert_eq!(cmds[0].name(), "add-formula");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -608,10 +608,8 @@ impl Effect for ImportJsonHeadless {
|
|||||||
let raw = if is_csv {
|
let raw = if is_csv {
|
||||||
serde_json::Value::Array(records.clone())
|
serde_json::Value::Array(records.clone())
|
||||||
} else {
|
} else {
|
||||||
serde_json::from_str(
|
serde_json::from_str(&std::fs::read_to_string(&self.path).unwrap_or_default())
|
||||||
&std::fs::read_to_string(&self.path).unwrap_or_default(),
|
.unwrap_or(serde_json::Value::Array(records.clone()))
|
||||||
)
|
|
||||||
.unwrap_or(serde_json::Value::Array(records.clone()))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let pipeline = ImportPipeline {
|
let pipeline = ImportPipeline {
|
||||||
|
|||||||
Reference in New Issue
Block a user