Merge branch 'main' into refine-keybindings

This commit is contained in:
Nate Butler 2023-11-28 15:12:22 -05:00
commit 3855413725
22 changed files with 109 additions and 160 deletions

View file

@ -304,19 +304,16 @@ impl PickerDelegate for CommandPaletteDelegate {
}; };
Some( Some(
ListItem::new(ix) ListItem::new(ix).inset(true).selected(selected).child(
.variant(ui::ListItemVariant::Inset) h_stack()
.selected(selected) .w_full()
.child( .justify_between()
h_stack() .child(HighlightedLabel::new(
.w_full() command.name.clone(),
.justify_between() r#match.positions.clone(),
.child(HighlightedLabel::new( ))
command.name.clone(), .children(KeyBinding::for_action(&*command.action, cx)),
r#match.positions.clone(), ),
))
.children(KeyBinding::for_action(&*command.action, cx)),
),
) )
} }
} }

View file

@ -719,7 +719,7 @@ impl PickerDelegate for FileFinderDelegate {
self.labels_for_match(path_match, cx, ix); self.labels_for_match(path_match, cx, ix);
Some( Some(
ListItem::new(ix).selected(selected).child( ListItem::new(ix).inset(true).selected(selected).child(
v_stack() v_stack()
.child(HighlightedLabel::new(file_name, file_name_positions)) .child(HighlightedLabel::new(file_name, file_name_positions))
.child(HighlightedLabel::new(full_path, full_path_positions)), .child(HighlightedLabel::new(full_path, full_path_positions)),

View file

@ -61,6 +61,7 @@ impl PickerDelegate for Delegate {
Some( Some(
ListItem::new(ix) ListItem::new(ix)
.inset(true)
.selected(selected) .selected(selected)
.child(Label::new(candidate)), .child(Label::new(candidate)),
) )

View file

@ -264,7 +264,7 @@ impl<M: ManagedView> Element for MenuHandle<M> {
let new_menu = (builder)(cx); let new_menu = (builder)(cx);
let menu2 = menu.clone(); let menu2 = menu.clone();
cx.subscribe(&new_menu, move |modal, e, cx| match e { cx.subscribe(&new_menu, move |_modal, e, cx| match e {
&DismissEvent::Dismiss => { &DismissEvent::Dismiss => {
*menu2.borrow_mut() = None; *menu2.borrow_mut() = None;
cx.notify(); cx.notify();

View file

@ -49,17 +49,4 @@ impl Divider {
self.inset = true; self.inset = true;
self self
} }
fn render(self, cx: &mut WindowContext) -> impl Element {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
}
DividerDirection::Vertical => {
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
}
})
.bg(cx.theme().colors().border_variant)
}
} }

View file

@ -203,17 +203,4 @@ impl IconElement {
self.size = size; self.size = size;
self self
} }
fn render(self, cx: &mut WindowContext) -> impl Element {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
};
svg()
.size(svg_size)
.flex_none()
.path(self.path)
.text_color(self.color.color(cx))
}
} }

View file

@ -23,15 +23,13 @@ impl RenderOnce for IconButton {
_ => self.color, _ => self.color,
}; };
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant { let (mut bg_color, bg_active_color) = match self.variant {
ButtonVariant::Filled => ( ButtonVariant::Filled => (
cx.theme().colors().element_background, cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active, cx.theme().colors().element_active,
), ),
ButtonVariant::Ghost => ( ButtonVariant::Ghost => (
cx.theme().colors().ghost_element_background, cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active, cx.theme().colors().ghost_element_active,
), ),
}; };
@ -124,6 +122,6 @@ impl IconButton {
} }
pub fn action(self, action: Box<dyn Action>) -> Self { pub fn action(self, action: Box<dyn Action>) -> Self {
self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone())) self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
} }
} }

View file

@ -13,7 +13,7 @@ pub struct KeyBinding {
impl RenderOnce for KeyBinding { impl RenderOnce for KeyBinding {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
h_stack() h_stack()
.flex_none() .flex_none()
.gap_2() .gap_2()

View file

@ -1,6 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use crate::styled_ext::StyledExt; use crate::styled_ext::StyledExt;
use gpui::{relative, Div, Hsla, IntoElement, StyledText, TextRun, WindowContext}; use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum LabelSize { pub enum LabelSize {
@ -182,9 +182,3 @@ impl HighlightedLabel {
self self
} }
} }
/// A run of text that receives the same style.
struct Run {
pub text: String,
pub color: Hsla,
}

View file

@ -12,14 +12,6 @@ use crate::{
}; };
use crate::{prelude::*, GraphicSlot}; use crate::{prelude::*, GraphicSlot};
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum ListItemVariant {
/// The list item extends to the far left and right of the list.
FullWidth,
#[default]
Inset,
}
pub enum ListHeaderMeta { pub enum ListHeaderMeta {
Tools(Vec<IconButton>), Tools(Vec<IconButton>),
// TODO: This should be a button // TODO: This should be a button
@ -32,8 +24,39 @@ pub struct ListHeader {
label: SharedString, label: SharedString,
left_icon: Option<Icon>, left_icon: Option<Icon>,
meta: Option<ListHeaderMeta>, meta: Option<ListHeaderMeta>,
variant: ListItemVariant,
toggle: Toggle, toggle: Toggle,
inset: bool,
}
impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
left_icon: None,
meta: None,
inset: false,
toggle: Toggle::NotToggleable,
}
}
pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn right_button(self, button: IconButton) -> Self {
self.meta(Some(ListHeaderMeta::Tools(vec![button])))
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
} }
impl RenderOnce for ListHeader { impl RenderOnce for ListHeader {
@ -61,7 +84,7 @@ impl RenderOnce for ListHeader {
.child( .child(
div() div()
.h_5() .h_5()
.when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .when(self.inset, |this| this.px_2())
.flex() .flex()
.flex_1() .flex_1()
.items_center() .items_center()
@ -90,42 +113,11 @@ impl RenderOnce for ListHeader {
} }
} }
impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
left_icon: None,
meta: None,
variant: ListItemVariant::default(),
toggle: Toggle::NotToggleable,
}
}
pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn right_button(self, button: IconButton) -> Self {
self.meta(Some(ListHeaderMeta::Tools(vec![button])))
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
}
#[derive(IntoElement, Clone)] #[derive(IntoElement, Clone)]
pub struct ListSubHeader { pub struct ListSubHeader {
label: SharedString, label: SharedString,
left_icon: Option<Icon>, left_icon: Option<Icon>,
variant: ListItemVariant, inset: bool,
} }
impl ListSubHeader { impl ListSubHeader {
@ -133,7 +125,7 @@ impl ListSubHeader {
Self { Self {
label: label.into(), label: label.into(),
left_icon: None, left_icon: None,
variant: ListItemVariant::default(), inset: false,
} }
} }
@ -146,11 +138,11 @@ impl ListSubHeader {
impl RenderOnce for ListSubHeader { impl RenderOnce for ListSubHeader {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
h_stack().flex_1().w_full().relative().py_1().child( h_stack().flex_1().w_full().relative().py_1().child(
div() div()
.h_6() .h_6()
.when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .when(self.inset, |this| this.px_2())
.flex() .flex()
.flex_1() .flex_1()
.w_full() .w_full()
@ -176,16 +168,14 @@ impl RenderOnce for ListSubHeader {
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ListItem { pub struct ListItem {
id: ElementId, id: ElementId,
disabled: bool,
selected: bool, selected: bool,
// TODO: Reintroduce this // TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility, // disclosure_control_style: DisclosureControlVisibility,
indent_level: usize, indent_level: usize,
indent_step_size: Pixels, indent_step_size: Pixels,
left_slot: Option<GraphicSlot>, left_slot: Option<GraphicSlot>,
overflow: OverflowStyle,
toggle: Toggle, toggle: Toggle,
variant: ListItemVariant, inset: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>, on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>, on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>, children: SmallVec<[AnyElement; 2]>,
@ -195,14 +185,12 @@ impl ListItem {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
id: id.into(), id: id.into(),
disabled: false,
selected: false, selected: false,
indent_level: 0, indent_level: 0,
indent_step_size: px(12.), indent_step_size: px(12.),
left_slot: None, left_slot: None,
overflow: OverflowStyle::Hidden,
toggle: Toggle::NotToggleable, toggle: Toggle::NotToggleable,
variant: ListItemVariant::default(), inset: false,
on_click: None, on_click: None,
on_secondary_mouse_down: None, on_secondary_mouse_down: None,
children: SmallVec::new(), children: SmallVec::new(),
@ -222,8 +210,8 @@ impl ListItem {
self self
} }
pub fn variant(mut self, variant: ListItemVariant) -> Self { pub fn inset(mut self, inset: bool) -> Self {
self.variant = variant; self.inset = inset;
self self
} }
@ -283,20 +271,26 @@ impl RenderOnce for ListItem {
div() div()
.id(self.id) .id(self.id)
.relative() .relative()
.hover(|mut style| {
style.background = Some(cx.theme().colors().editor_background.into());
style
})
// TODO: Add focus state // TODO: Add focus state
// .when(self.state == InteractionState::Focused, |this| { // .when(self.state == InteractionState::Focused, |this| {
// this.border() // this.border()
// .border_color(cx.theme().colors().border_focused) // .border_color(cx.theme().colors().border_focused)
// }) // })
.when(self.inset, |this| this.rounded_md())
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.active(|style| style.bg(cx.theme().colors().ghost_element_active)) .active(|style| style.bg(cx.theme().colors().ghost_element_active))
.when(self.selected, |this| { .when(self.selected, |this| {
this.bg(cx.theme().colors().ghost_element_selected) this.bg(cx.theme().colors().ghost_element_selected)
}) })
.when_some(self.on_click.clone(), |this, on_click| {
this.on_click(move |event, cx| {
// HACK: GPUI currently fires `on_click` with any mouse button,
// but we only care about the left button.
if event.down.button == MouseButton::Left {
(on_click)(event, cx)
}
})
})
.when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
this.on_mouse_down(MouseButton::Right, move |event, cx| { this.on_mouse_down(MouseButton::Right, move |event, cx| {
(on_mouse_down)(event, cx) (on_mouse_down)(event, cx)
@ -304,7 +298,7 @@ impl RenderOnce for ListItem {
}) })
.child( .child(
div() div()
.when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .when(self.inset, |this| this.px_2())
.ml(self.indent_level as f32 * self.indent_step_size) .ml(self.indent_level as f32 * self.indent_step_size)
.flex() .flex()
.gap_1() .gap_1()
@ -367,7 +361,7 @@ pub struct List {
impl RenderOnce for List { impl RenderOnce for List {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
let list_content = match (self.children.is_empty(), self.toggle) { let list_content = match (self.children.is_empty(), self.toggle) {
(false, _) => div().children(self.children), (false, _) => div().children(self.children),
(true, Toggle::Toggled(false)) => div(), (true, Toggle::Toggled(false)) => div(),

View file

@ -9,7 +9,7 @@ pub struct AvatarStory;
impl Render for AvatarStory { impl Render for AvatarStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<Avatar>()) .child(Story::title_for::<Avatar>())
.child(Story::label("Default")) .child(Story::label("Default"))

View file

@ -10,7 +10,7 @@ pub struct ButtonStory;
impl Render for ButtonStory { impl Render for ButtonStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
let states = InteractionState::iter(); let states = InteractionState::iter();
Story::container() Story::container()
@ -139,7 +139,7 @@ impl Render for ButtonStory {
.child( .child(
Button::new("Label") Button::new("Label")
.variant(ButtonVariant::Ghost) .variant(ButtonVariant::Ghost)
.on_click(|_, cx| println!("Button clicked.")), .on_click(|_, _cx| println!("Button clicked.")),
) )
} }
} }

View file

@ -10,11 +10,11 @@ fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<C
ContextMenu::build(cx, |menu, _| { ContextMenu::build(cx, |menu, _| {
menu.header(header) menu.header(header)
.separator() .separator()
.entry("Print current time", |v, cx| { .entry("Print current time", |_event, cx| {
println!("dispatching PrintCurrentTime action"); println!("dispatching PrintCurrentTime action");
cx.dispatch_action(PrintCurrentDate.boxed_clone()) cx.dispatch_action(PrintCurrentDate.boxed_clone())
}) })
.entry("Print best foot", |v, cx| { .entry("Print best foot", |_event, cx| {
cx.dispatch_action(PrintBestFood.boxed_clone()) cx.dispatch_action(PrintBestFood.boxed_clone())
}) })
}) })
@ -25,7 +25,7 @@ pub struct ContextMenuStory;
impl Render for ContextMenuStory { impl Render for ContextMenuStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.on_action(|_: &PrintCurrentDate, _| { .on_action(|_: &PrintCurrentDate, _| {
println!("printing unix time!"); println!("printing unix time!");

View file

@ -10,7 +10,7 @@ pub struct IconStory;
impl Render for IconStory { impl Render for IconStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
let icons = Icon::iter(); let icons = Icon::iter();
Story::container() Story::container()

View file

@ -9,7 +9,7 @@ pub struct InputStory;
impl Render for InputStory { impl Render for InputStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<Input>()) .child(Story::title_for::<Input>())
.child(Story::label("Default")) .child(Story::label("Default"))

View file

@ -16,7 +16,7 @@ pub fn binding(key: &str) -> gpui::KeyBinding {
impl Render for KeybindingStory { impl Render for KeybindingStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2); let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container() Story::container()

View file

@ -9,7 +9,7 @@ pub struct LabelStory;
impl Render for LabelStory { impl Render for LabelStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<Label>()) .child(Story::title_for::<Label>())
.child(Story::label("Default")) .child(Story::label("Default"))

View file

@ -9,7 +9,7 @@ pub struct ListItemStory;
impl Render for ListItemStory { impl Render for ListItemStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<ListItem>()) .child(Story::title_for::<ListItem>())
.child(Story::label("Default")) .child(Story::label("Default"))

View file

@ -13,7 +13,7 @@ pub struct Tooltip {
impl Tooltip { impl Tooltip {
pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView { pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
cx.build_view(|cx| Self { cx.build_view(|_cx| Self {
title: title.into(), title: title.into(),
meta: None, meta: None,
key_binding: None, key_binding: None,

View file

@ -16,12 +16,6 @@ pub enum IconSide {
Right, Right,
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)]
pub enum OverflowStyle {
Hidden,
Wrap,
}
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)] #[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
pub enum InteractionState { pub enum InteractionState {
/// An element that is enabled and not hovered, active, focused, or disabled. /// An element that is enabled and not hovered, active, focused, or disabled.

View file

@ -11,8 +11,6 @@
#![doc = include_str!("../docs/hello-world.md")] #![doc = include_str!("../docs/hello-world.md")]
#![doc = include_str!("../docs/building-ui.md")] #![doc = include_str!("../docs/building-ui.md")]
#![doc = include_str!("../docs/todo.md")] #![doc = include_str!("../docs/todo.md")]
// TODO: Fix warnings instead of supressing.
#![allow(dead_code, unused_variables)]
mod components; mod components;
pub mod prelude; pub mod prelude;

View file

@ -16,55 +16,54 @@ fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String { fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
let suffix = if distance < 0 { " from now" } else { " ago" }; let suffix = if distance < 0 { " from now" } else { " ago" };
let d = distance.abs(); let distance = distance.abs();
let minutes = d / 60; let minutes = distance / 60;
let hours = d / 3600; let hours = distance / 3_600;
let days = d / 86400; let days = distance / 86_400;
let months = d / 2592000; let months = distance / 2_592_000;
let years = d / 31536000;
let string = if d < 5 && include_seconds { let string = if distance < 5 && include_seconds {
"less than 5 seconds".to_string() "less than 5 seconds".to_string()
} else if d < 10 && include_seconds { } else if distance < 10 && include_seconds {
"less than 10 seconds".to_string() "less than 10 seconds".to_string()
} else if d < 20 && include_seconds { } else if distance < 20 && include_seconds {
"less than 20 seconds".to_string() "less than 20 seconds".to_string()
} else if d < 40 && include_seconds { } else if distance < 40 && include_seconds {
"half a minute".to_string() "half a minute".to_string()
} else if d < 60 && include_seconds { } else if distance < 60 && include_seconds {
"less than a minute".to_string() "less than a minute".to_string()
} else if d < 90 && include_seconds { } else if distance < 90 && include_seconds {
"1 minute".to_string() "1 minute".to_string()
} else if d < 30 { } else if distance < 30 {
"less than a minute".to_string() "less than a minute".to_string()
} else if d < 90 { } else if distance < 90 {
"1 minute".to_string() "1 minute".to_string()
} else if d < 2700 { } else if distance < 2_700 {
format!("{} minutes", minutes) format!("{} minutes", minutes)
} else if d < 5400 { } else if distance < 5_400 {
"about 1 hour".to_string() "about 1 hour".to_string()
} else if d < 86400 { } else if distance < 86_400 {
format!("about {} hours", hours) format!("about {} hours", hours)
} else if d < 172800 { } else if distance < 172_800 {
"1 day".to_string() "1 day".to_string()
} else if d < 2592000 { } else if distance < 2_592_000 {
format!("{} days", days) format!("{} days", days)
} else if d < 5184000 { } else if distance < 5_184_000 {
"about 1 month".to_string() "about 1 month".to_string()
} else if d < 7776000 { } else if distance < 7_776_000 {
"about 2 months".to_string() "about 2 months".to_string()
} else if d < 31540000 { } else if distance < 31_540_000 {
format!("{} months", months) format!("{} months", months)
} else if d < 39425000 { } else if distance < 39_425_000 {
"about 1 year".to_string() "about 1 year".to_string()
} else if d < 55195000 { } else if distance < 55_195_000 {
"over 1 year".to_string() "over 1 year".to_string()
} else if d < 63080000 { } else if distance < 63_080_000 {
"almost 2 years".to_string() "almost 2 years".to_string()
} else { } else {
let years = d / 31536000; let years = distance / 31_536_000;
let remaining_months = (d % 31536000) / 2592000; let remaining_months = (distance % 31_536_000) / 2_592_000;
if remaining_months < 3 { if remaining_months < 3 {
format!("about {} years", years) format!("about {} years", years)
@ -76,7 +75,7 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
}; };
if add_suffix { if add_suffix {
return format!("{}{}", string, suffix); format!("{}{}", string, suffix)
} else { } else {
string string
} }