From 41d30f82fd0ef88c6df563bacae6f78f1ac3a922 Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 7 Feb 2025 19:32:49 +0200 Subject: [PATCH] reorganize shared input components --- src/app/browser/window/tab/item/page/input.rs | 2 +- .../window/tab/item/page/input/titan.rs | 90 ++++++++++-- .../page/input/titan/{text => }/control.rs | 23 +-- .../item/page/input/titan/control/counter.rs | 54 +++++++ .../input/titan/{text => }/control/options.rs | 0 .../input/titan/{file => }/control/upload.rs | 0 .../window/tab/item/page/input/titan/file.rs | 139 ++++++++---------- .../tab/item/page/input/titan/file/control.rs | 58 -------- .../page/input/titan/file/control/counter.rs | 26 ---- .../page/input/titan/file/control/options.rs | 38 ----- .../tab/item/page/input/titan/file/form.rs | 22 --- .../page/input/titan/{title.rs => tab.rs} | 8 +- .../window/tab/item/page/input/titan/text.rs | 83 ++++------- .../page/input/titan/text/control/counter.rs | 31 ---- .../page/input/titan/text/control/upload.rs | 30 ---- .../tab/item/page/input/titan/text/form.rs | 14 +- 16 files changed, 238 insertions(+), 380 deletions(-) rename src/app/browser/window/tab/item/page/input/titan/{text => }/control.rs (64%) create mode 100644 src/app/browser/window/tab/item/page/input/titan/control/counter.rs rename src/app/browser/window/tab/item/page/input/titan/{text => }/control/options.rs (100%) rename src/app/browser/window/tab/item/page/input/titan/{file => }/control/upload.rs (100%) delete mode 100644 src/app/browser/window/tab/item/page/input/titan/file/control.rs delete mode 100644 src/app/browser/window/tab/item/page/input/titan/file/control/counter.rs delete mode 100644 src/app/browser/window/tab/item/page/input/titan/file/control/options.rs delete mode 100644 src/app/browser/window/tab/item/page/input/titan/file/form.rs rename src/app/browser/window/tab/item/page/input/titan/{title.rs => tab.rs} (55%) delete mode 100644 src/app/browser/window/tab/item/page/input/titan/text/control/counter.rs delete mode 100644 src/app/browser/window/tab/item/page/input/titan/text/control/upload.rs diff --git a/src/app/browser/window/tab/item/page/input.rs b/src/app/browser/window/tab/item/page/input.rs index 59690bb4..5becc41a 100644 --- a/src/app/browser/window/tab/item/page/input.rs +++ b/src/app/browser/window/tab/item/page/input.rs @@ -75,6 +75,6 @@ impl Input { } pub fn set_new_titan(&self, on_send: impl Fn(titan::Header, Bytes, Box) + 'static) { - self.update(Some(>k::Notebook::titan(on_send))); + self.update(Some(>k::Box::titan(on_send))); } } diff --git a/src/app/browser/window/tab/item/page/input/titan.rs b/src/app/browser/window/tab/item/page/input/titan.rs index 944185d8..c0c7369d 100644 --- a/src/app/browser/window/tab/item/page/input/titan.rs +++ b/src/app/browser/window/tab/item/page/input/titan.rs @@ -1,35 +1,93 @@ +mod control; mod file; mod header; +mod tab; mod text; -mod title; +use control::Control; use file::File; -use gtk::{ - glib::{uuid_string_random, Bytes}, - Notebook, -}; +use gtk::{glib::Bytes, Notebook}; pub use header::Header; +use tab::Tab; use text::Text; -use title::Title; pub trait Titan { fn titan(callback: impl Fn(Header, Bytes, Box) + 'static) -> Self; } -impl Titan for Notebook { +impl Titan for gtk::Box { fn titan(callback: impl Fn(Header, Bytes, Box) + 'static) -> Self { - use gtk::{Box, Label}; + use gtk::{glib::uuid_string_random, Box, Label, TextView}; + use std::{cell::Cell, rc::Rc}; - let notebook = Notebook::builder() - .name(format!("s{}", uuid_string_random())) - .show_border(false) - .build(); + // Init components + let header = Rc::new(Cell::new(Header { + mime: None, + token: None, + })); + let control = Rc::new(Control::build(&header)); - notebook.append_page(&Box::text(callback), Some(&Label::title("Text"))); - notebook.append_page(&Box::file(), Some(&Label::title("File"))); + let text = TextView::text(&control); + let file = File::build(&control); - notebook_css_patch(¬ebook); - notebook + let notebook = { + let notebook = Notebook::builder() + .name(format!("s{}", uuid_string_random())) + .show_border(false) + .build(); + + notebook.append_page(&text, Some(&Label::tab("Text"))); + notebook.append_page(&file.button, Some(&Label::tab("File"))); + + notebook.connect_switch_page({ + let control = control.clone(); + let text = text.clone(); + move |_, _, i| { + if i == 0 { + control.update(Some(text.len()), Some(text.count())) + } else { + control.update(file.size(), None) + } + } + }); + + notebook_css_patch(¬ebook); + notebook + }; + + // Init main widget + let g_box = { + use gtk::{prelude::BoxExt, Orientation}; + + let g_box = { + const MARGIN: i32 = 8; + Box::builder() + .margin_end(MARGIN) + .margin_start(MARGIN) + .orientation(Orientation::Vertical) + .spacing(MARGIN) + .build() + }; + + g_box.append(¬ebook); + g_box.append(&control.g_box); + g_box + }; + + // Init events + /*control.upload.connect_clicked(move |this| { + this.set_uploading(); + callback( + header.take(), + Bytes::from(form.text().as_bytes()), + Box::new({ + let this = this.clone(); + move || this.set_resend() // on failure + }), + ) + });*/ + + g_box } } diff --git a/src/app/browser/window/tab/item/page/input/titan/text/control.rs b/src/app/browser/window/tab/item/page/input/titan/control.rs similarity index 64% rename from src/app/browser/window/tab/item/page/input/titan/text/control.rs rename to src/app/browser/window/tab/item/page/input/titan/control.rs index 1889a764..1da9fe65 100644 --- a/src/app/browser/window/tab/item/page/input/titan/text/control.rs +++ b/src/app/browser/window/tab/item/page/input/titan/control.rs @@ -12,8 +12,6 @@ use options::Options; use std::{cell::Cell, rc::Rc}; pub use upload::Upload; -const SPACING: i32 = 8; - pub struct Control { pub counter: Label, pub upload: Button, @@ -31,11 +29,15 @@ impl Control { let upload = Button::upload(); // Init main widget - let g_box = Box::builder() - .halign(Align::End) - .orientation(Orientation::Horizontal) - .spacing(SPACING) - .build(); + let g_box = { + const MARGIN: i32 = 8; + Box::builder() + .halign(Align::End) + .margin_bottom(MARGIN) + .orientation(Orientation::Horizontal) + .spacing(MARGIN) + .build() + }; g_box.append(&counter); g_box.append(&options); @@ -50,9 +52,10 @@ impl Control { } // Actions - pub fn update(&self, chars_count: i32, bytes_total: usize) { + pub fn update(&self, bytes_total: Option, chars_count: Option) { // Update children components - self.counter.update(chars_count, bytes_total); - self.upload.set_sensitive(bytes_total > 0); + self.counter.update(bytes_total, chars_count); + self.upload + .set_sensitive(bytes_total.is_some_and(|this| this > 0)); } } diff --git a/src/app/browser/window/tab/item/page/input/titan/control/counter.rs b/src/app/browser/window/tab/item/page/input/titan/control/counter.rs new file mode 100644 index 00000000..a619a20e --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/control/counter.rs @@ -0,0 +1,54 @@ +use gtk::Label; + +pub trait Counter { + fn counter() -> Self; + fn update(&self, bytes_total: Option, chars_count: Option); +} + +impl Counter for Label { + // Constructors + + fn counter() -> Self { + Label::builder().css_classes(["dim-label"]).build() // @TODO use `dimmed` in Adw 1.6, + } + + // Actions + + fn update(&self, bytes_total: Option, chars_count: Option) { + use gtk::prelude::WidgetExt; + + self.set_visible(if let Some(bytes_total) = bytes_total { + if let Some(chars_count) = chars_count { + if chars_count > 0 { + self.set_label(&bytes_total.to_string()); + self.set_tooltip_markup(Some(&format_text_tooltip(bytes_total, chars_count))); + true + } else { + false + } + } else { + self.set_label(&format_file_tooltip(bytes_total)); + self.set_tooltip_markup(None); + true + } + } else { + false + }); + } +} + +// Tools + +fn format_file_tooltip(bytes_total: usize) -> String { + use crate::tool::Format; + bytes_total.bytes() +} + +fn format_text_tooltip(bytes_total: usize, chars_count: i32) -> String { + use plurify::Plurify; + format!( + "{bytes_total} {} / {chars_count} {}", + (bytes_total).plurify(&["byte", "bytes", "bytes"]), + (chars_count as usize).plurify(&["char", "chars", "chars"]), + ) +} diff --git a/src/app/browser/window/tab/item/page/input/titan/text/control/options.rs b/src/app/browser/window/tab/item/page/input/titan/control/options.rs similarity index 100% rename from src/app/browser/window/tab/item/page/input/titan/text/control/options.rs rename to src/app/browser/window/tab/item/page/input/titan/control/options.rs diff --git a/src/app/browser/window/tab/item/page/input/titan/file/control/upload.rs b/src/app/browser/window/tab/item/page/input/titan/control/upload.rs similarity index 100% rename from src/app/browser/window/tab/item/page/input/titan/file/control/upload.rs rename to src/app/browser/window/tab/item/page/input/titan/control/upload.rs diff --git a/src/app/browser/window/tab/item/page/input/titan/file.rs b/src/app/browser/window/tab/item/page/input/titan/file.rs index d83e7773..df78163c 100644 --- a/src/app/browser/window/tab/item/page/input/titan/file.rs +++ b/src/app/browser/window/tab/item/page/input/titan/file.rs @@ -1,97 +1,86 @@ -mod control; -mod form; +use super::Control; +use gtk::{glib::Bytes, Button}; +use std::{cell::RefCell, rc::Rc}; -use super::Header; -use control::Control; -use gtk::Box; - -pub trait File { - fn file() -> Self; +pub struct File { + buffer: Rc>>, + pub button: Button, } -impl File for Box { - fn file() -> Self { - use form::Form; +impl File { + pub fn build(control: &Rc) -> Self { use gtk::{ gio::Cancellable, prelude::{ButtonExt, FileExt, WidgetExt}, Button, FileDialog, Window, }; - use std::{cell::Cell, rc::Rc}; // Init components - let header = Rc::new(Cell::new(Header { - mime: None, - token: None, - })); - let control = Rc::new(Control::build(&header)); - let form = Button::form(); + let buffer = Rc::new(RefCell::new(None)); - // Init main widget - let g_box = { - use gtk::{prelude::BoxExt, Orientation}; - - const MARGIN: i32 = 8; - - let g_box = Box::builder() - .margin_end(MARGIN) - .margin_start(MARGIN) - .orientation(Orientation::Vertical) - .spacing(MARGIN) - .build(); - - g_box.append(&form); - g_box.append(&control.g_box); - g_box - }; + let button = Button::builder() + .label("Choose a file..") + .margin_top(4) + .build(); // Init events - form.connect_clicked(move |form| { - const CLASS: (&str, &str, &str) = ("error", "warning", "success"); + button.connect_clicked({ + let control = control.clone(); + let buffer = buffer.clone(); + move |this| { + const CLASS: (&str, &str, &str) = ("error", "warning", "success"); - // reset - control.update(None); - form.set_sensitive(false); - form.remove_css_class(CLASS.0); - form.remove_css_class(CLASS.1); - form.remove_css_class(CLASS.2); + // reset + control.update(None, None); + this.set_sensitive(false); + this.remove_css_class(CLASS.0); + this.remove_css_class(CLASS.1); + this.remove_css_class(CLASS.2); - FileDialog::builder() - .build() - .open(Window::NONE, Cancellable::NONE, { - let control = control.clone(); - let form = form.clone(); - move |result| match result { - Ok(file) => match file.path() { - Some(path) => { - form.set_label("Buffering, please wait.."); - file.load_bytes_async(Cancellable::NONE, move |result| match result - { - Ok((bytes, _)) => { - control.update(Some(bytes.len())); + FileDialog::builder() + .build() + .open(Window::NONE, Cancellable::NONE, { + let control = control.clone(); + let buffer = buffer.clone(); + let this = this.clone(); + move |result| match result { + Ok(file) => match file.path() { + Some(path) => { + this.set_label("Buffering, please wait.."); // @TODO progress + file.load_bytes_async(Cancellable::NONE, move |result| { + match result { + Ok((bytes, _)) => { + control.update(Some(bytes.len()), None); + buffer.replace(Some(bytes)); - form.set_label(path.to_str().unwrap()); - form.set_css_classes(&[CLASS.2]); - form.set_sensitive(true); - } - Err(e) => { - form.set_css_classes(&[CLASS.0]); - form.set_label(e.message()); - form.set_sensitive(true); - } - }) + this.set_css_classes(&[CLASS.2]); + this.set_label(path.to_str().unwrap()); + this.set_sensitive(true); + } + Err(e) => { + this.set_css_classes(&[CLASS.0]); + this.set_label(e.message()); + this.set_sensitive(true); + } + } + }) + } + None => todo!(), + }, + Err(e) => { + this.set_css_classes(&[CLASS.1]); + this.set_label(e.message()); + this.set_sensitive(true); } - None => todo!(), - }, - Err(e) => { - form.set_css_classes(&[CLASS.1]); - form.set_label(e.message()); - form.set_sensitive(true); } - } - }); + }); + } }); - g_box + Self { buffer, button } + } + + pub fn size(&self) -> Option { + self.buffer.borrow().as_ref().map(|bytes| bytes.len()) } } diff --git a/src/app/browser/window/tab/item/page/input/titan/file/control.rs b/src/app/browser/window/tab/item/page/input/titan/file/control.rs deleted file mode 100644 index 9d8b8254..00000000 --- a/src/app/browser/window/tab/item/page/input/titan/file/control.rs +++ /dev/null @@ -1,58 +0,0 @@ -mod counter; -mod options; -mod upload; - -use super::Header; -use counter::Counter; -use gtk::{Box, Button, Label}; -use options::Options; -use std::{cell::Cell, rc::Rc}; -use upload::Upload; - -pub struct Control { - counter: Label, - options: Button, - upload: Button, - pub g_box: Box, -} - -impl Control { - pub fn build(header: &Rc>) -> Self { - // Init components - let counter = Label::counter(); - let options = Button::options(header); - let upload = Button::upload(); - - // Init main widget - let g_box = { - use gtk::{prelude::BoxExt, Align, Orientation}; - let g_box = Box::builder() - .halign(Align::End) - .orientation(Orientation::Horizontal) - .spacing(8) - .build(); - - g_box.append(&counter); - g_box.append(&options); - g_box.append(&upload); - g_box - }; - - Self { - counter, - options, - upload, - g_box, - } - } - - pub fn update(&self, bytes_total: Option) { - use gtk::prelude::WidgetExt; - - self.counter.update(bytes_total); - - let is_some = bytes_total.is_some(); - self.options.set_sensitive(is_some); - self.upload.set_sensitive(is_some); - } -} diff --git a/src/app/browser/window/tab/item/page/input/titan/file/control/counter.rs b/src/app/browser/window/tab/item/page/input/titan/file/control/counter.rs deleted file mode 100644 index cb00eb6b..00000000 --- a/src/app/browser/window/tab/item/page/input/titan/file/control/counter.rs +++ /dev/null @@ -1,26 +0,0 @@ -use gtk::{prelude::WidgetExt, Label}; - -pub trait Counter { - fn counter() -> Self; - fn update(&self, bytes_total: Option); -} - -impl Counter for Label { - // Constructors - - fn counter() -> Self { - Label::builder().css_classes(["dim-label"]).build() // @TODO use `dimmed` in Adw 1.6, - } - - // Actions - - fn update(&self, bytes_total: Option) { - self.set_visible(if let Some(bytes_total) = bytes_total { - use crate::tool::Format; - self.set_text(&bytes_total.bytes()); - true - } else { - false - }) - } -} diff --git a/src/app/browser/window/tab/item/page/input/titan/file/control/options.rs b/src/app/browser/window/tab/item/page/input/titan/file/control/options.rs deleted file mode 100644 index 8de5838f..00000000 --- a/src/app/browser/window/tab/item/page/input/titan/file/control/options.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::Header; -use gtk::{ - prelude::{ButtonExt, WidgetExt}, - Button, -}; -use std::{cell::Cell, rc::Rc}; - -pub trait Options { - fn options(header: &Rc>) -> Self; -} - -impl Options for Button { - fn options(header: &Rc>) -> Self { - let button = Button::builder() - .icon_name("emblem-system-symbolic") - // @TODO deactivate by default on dyn MIME type detection only - // .sensitive(false) - .tooltip_text("Options") - .build(); - - button.connect_clicked({ - let header = header.clone(); - move |this| { - this.set_sensitive(false); // lock - header.take().dialog(Some(this), { - let this = this.clone(); - let header = header.clone(); - move |options| { - header.replace(options); - this.set_sensitive(true); // unlock - } - }) - } - }); - - button - } -} diff --git a/src/app/browser/window/tab/item/page/input/titan/file/form.rs b/src/app/browser/window/tab/item/page/input/titan/file/form.rs deleted file mode 100644 index f146e332..00000000 --- a/src/app/browser/window/tab/item/page/input/titan/file/form.rs +++ /dev/null @@ -1,22 +0,0 @@ -use gtk::Button; - -pub trait Form { - fn form() -> Self; -} - -impl Form for Button { - fn form() -> Self { - use gtk::prelude::{ButtonExt, WidgetExt}; - - let button = Button::builder() - .label("Choose a file..") - .margin_top(4) - .build(); - - button.connect_clicked(|this| { - this.set_sensitive(false); // lock - }); - - button - } -} diff --git a/src/app/browser/window/tab/item/page/input/titan/title.rs b/src/app/browser/window/tab/item/page/input/titan/tab.rs similarity index 55% rename from src/app/browser/window/tab/item/page/input/titan/title.rs rename to src/app/browser/window/tab/item/page/input/titan/tab.rs index 6faef878..14b9278d 100644 --- a/src/app/browser/window/tab/item/page/input/titan/title.rs +++ b/src/app/browser/window/tab/item/page/input/titan/tab.rs @@ -1,11 +1,11 @@ use gtk::Label; -pub trait Title { - fn title(label: &str) -> Self; +pub trait Tab { + fn tab(label: &str) -> Self; } -impl Title for Label { - fn title(label: &str) -> Self { +impl Tab for Label { + fn tab(label: &str) -> Self { Label::builder() .css_classes(["heading"]) .label(label) diff --git a/src/app/browser/window/tab/item/page/input/titan/text.rs b/src/app/browser/window/tab/item/page/input/titan/text.rs index 54fb3d71..4878e075 100644 --- a/src/app/browser/window/tab/item/page/input/titan/text.rs +++ b/src/app/browser/window/tab/item/page/input/titan/text.rs @@ -1,71 +1,42 @@ -mod control; mod form; -use super::Header; -use gtk::glib::Bytes; +use super::Control; +use gtk::{ + prelude::{TextBufferExt, TextViewExt}, + TextView, +}; +use std::rc::Rc; pub trait Text { - fn text(callback: impl Fn(Header, Bytes, Box) + 'static) -> Self; + fn text(control: &Rc) -> Self; + fn len(&self) -> usize; + fn count(&self) -> i32; } -impl Text for gtk::Box { - fn text(callback: impl Fn(Header, Bytes, Box) + 'static) -> Self { - use control::{Control, Upload}; +impl Text for TextView { + fn text(control: &Rc) -> Self { use form::Form; - use gtk::{ - prelude::{BoxExt, ButtonExt, TextBufferExt, TextViewExt}, - Orientation, TextView, - }; - use std::{cell::Cell, rc::Rc}; - // Init components - let header = Rc::new(Cell::new(Header { - mime: Some("text/plain".into()), // some servers require not empty content type - token: None, - })); - let control = Rc::new(Control::build(&header)); - let form = TextView::form(); + let text_view = TextView::form(); - // Init widget - let g_box = { - const MARGIN: i32 = 8; - let g_box = gtk::Box::builder() - .margin_bottom(MARGIN / 2) - .margin_end(MARGIN) - .margin_start(MARGIN) - .orientation(Orientation::Vertical) - .spacing(MARGIN) - .build(); - - g_box.append(&form); - g_box.append(&control.g_box); - g_box - }; - - // Connect events - - form.buffer().connect_changed({ + text_view.buffer().connect_changed({ let control = control.clone(); - move |this| { - control.update( - this.char_count(), - this.text(&this.start_iter(), &this.end_iter(), true).len(), - ) - } + let text_view = text_view.clone(); + move |text_buffer| control.update(Some(text_view.len()), Some(text_buffer.char_count())) }); - control.upload.connect_clicked(move |this| { - this.set_uploading(); - callback( - header.take(), - Bytes::from(form.text().as_bytes()), - Box::new({ - let this = this.clone(); - move || this.set_resend() // on failure - }), - ) - }); + text_view + } - g_box + fn count(&self) -> i32 { + self.buffer().char_count() + } + + fn len(&self) -> usize { + let buffer = self.buffer(); + + buffer + .text(&buffer.start_iter(), &buffer.end_iter(), true) + .len() } } diff --git a/src/app/browser/window/tab/item/page/input/titan/text/control/counter.rs b/src/app/browser/window/tab/item/page/input/titan/text/control/counter.rs deleted file mode 100644 index 88ae80fc..00000000 --- a/src/app/browser/window/tab/item/page/input/titan/text/control/counter.rs +++ /dev/null @@ -1,31 +0,0 @@ -use gtk::{prelude::WidgetExt, Label}; -use plurify::Plurify; - -pub trait Counter { - fn counter() -> Self; - fn update(&self, char_count: i32, bytes_total: usize); -} - -impl Counter for Label { - // Constructors - - fn counter() -> Self { - Label::builder().css_classes(["dim-label"]).build() // @TODO use `dimmed` in Adw 1.6, - } - - // Actions - - fn update(&self, chars_count: i32, bytes_total: usize) { - self.set_visible(if bytes_total > 0 { - self.set_label(&bytes_total.to_string()); - self.set_tooltip_markup(Some(&format!( - "{bytes_total} {} / {chars_count} {}", - (bytes_total).plurify(&["byte", "bytes", "bytes"]), - (chars_count as usize).plurify(&["char", "chars", "chars"]), - ))); - true - } else { - false - }) - } -} diff --git a/src/app/browser/window/tab/item/page/input/titan/text/control/upload.rs b/src/app/browser/window/tab/item/page/input/titan/text/control/upload.rs deleted file mode 100644 index c6099c5a..00000000 --- a/src/app/browser/window/tab/item/page/input/titan/text/control/upload.rs +++ /dev/null @@ -1,30 +0,0 @@ -use gtk::{ - prelude::{ButtonExt, WidgetExt}, - Button, -}; - -pub trait Upload { - fn upload() -> Self; - fn set_uploading(&self); - fn set_resend(&self); -} - -impl Upload for Button { - fn upload() -> Self { - Button::builder() - // @TODO this class not looks well with default GTK Notebook widget - // activate it after upgrade to `ToggleGroup` in Adw v1.7 / Ubuntu 26.04 - // .css_classes(["accent"]) // | `suggested-action` - .label("Upload") - .sensitive(false) - .build() - } - fn set_uploading(&self) { - self.set_sensitive(false); - self.set_label("uploading.."); - } - fn set_resend(&self) { - self.set_sensitive(true); - self.set_label("Resend"); - } -} diff --git a/src/app/browser/window/tab/item/page/input/titan/text/form.rs b/src/app/browser/window/tab/item/page/input/titan/text/form.rs index 93f9bcde..088bbd10 100644 --- a/src/app/browser/window/tab/item/page/input/titan/text/form.rs +++ b/src/app/browser/window/tab/item/page/input/titan/text/form.rs @@ -1,14 +1,9 @@ -use gtk::{ - glib::GString, - prelude::{TextBufferExt, TextViewExt, WidgetExt}, - TextView, WrapMode, -}; +use gtk::{prelude::WidgetExt, TextView, WrapMode}; use libspelling::{Checker, TextBufferAdapter}; use sourceview::Buffer; pub trait Form { fn form() -> Self; - fn text(&self) -> GString; } impl Form for TextView { @@ -51,11 +46,4 @@ impl Form for TextView { // Return activated `Self` text_view } - - // Getters - - fn text(&self) -> GString { - let buffer = self.buffer(); - buffer.text(&buffer.start_iter(), &buffer.end_iter(), true) - } }