summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2025-01-31 17:35:38 +0100
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2025-01-31 17:35:38 +0100
commit128058ea948909c21a9cfd0b58cbd3a13e238e57 (patch)
tree5fee0cbc85d7f61250f6c4424e68677249ceea14 /examples
parent6aab76e3a0ce219a950f7214cd2ab68171c5df00 (diff)
downloadiced-128058ea948909c21a9cfd0b58cbd3a13e238e57.tar.gz
iced-128058ea948909c21a9cfd0b58cbd3a13e238e57.tar.bz2
iced-128058ea948909c21a9cfd0b58cbd3a13e238e57.zip
Draft incremental `markdown` parsing
Specially useful when dealing with long Markdown streams, like LLMs.
Diffstat (limited to 'examples')
-rw-r--r--examples/markdown/Cargo.toml2
-rw-r--r--examples/markdown/src/main.rs96
2 files changed, 86 insertions, 12 deletions
diff --git a/examples/markdown/Cargo.toml b/examples/markdown/Cargo.toml
index cb74b954..fa6ced74 100644
--- a/examples/markdown/Cargo.toml
+++ b/examples/markdown/Cargo.toml
@@ -7,6 +7,6 @@ publish = false
[dependencies]
iced.workspace = true
-iced.features = ["markdown", "highlighter", "debug"]
+iced.features = ["markdown", "highlighter", "tokio", "debug"]
open = "5.3"
diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs
index 5605478f..a55e91d2 100644
--- a/examples/markdown/src/main.rs
+++ b/examples/markdown/src/main.rs
@@ -1,23 +1,37 @@
use iced::highlighter;
-use iced::widget::{self, markdown, row, scrollable, text_editor};
-use iced::{Element, Fill, Font, Task, Theme};
+use iced::time::{self, milliseconds};
+use iced::widget::{
+ self, hover, markdown, right, row, scrollable, text_editor, toggler,
+};
+use iced::{Element, Fill, Font, Subscription, Task, Theme};
pub fn main() -> iced::Result {
iced::application("Markdown - Iced", Markdown::update, Markdown::view)
+ .subscription(Markdown::subscription)
.theme(Markdown::theme)
.run_with(Markdown::new)
}
struct Markdown {
content: text_editor::Content,
- items: Vec<markdown::Item>,
+ mode: Mode,
theme: Theme,
}
+enum Mode {
+ Oneshot(Vec<markdown::Item>),
+ Stream {
+ pending: String,
+ parsed: markdown::Content,
+ },
+}
+
#[derive(Debug, Clone)]
enum Message {
Edit(text_editor::Action),
LinkClicked(markdown::Url),
+ ToggleStream(bool),
+ NextToken,
}
impl Markdown {
@@ -29,7 +43,7 @@ impl Markdown {
(
Self {
content: text_editor::Content::with_text(INITIAL_CONTENT),
- items: markdown::parse(INITIAL_CONTENT).collect(),
+ mode: Mode::Oneshot(markdown::parse(INITIAL_CONTENT).collect()),
theme,
},
widget::focus_next(),
@@ -44,13 +58,48 @@ impl Markdown {
self.content.perform(action);
if is_edit {
- self.items =
- markdown::parse(&self.content.text()).collect();
+ self.mode = match self.mode {
+ Mode::Oneshot(_) => Mode::Oneshot(
+ markdown::parse(&self.content.text()).collect(),
+ ),
+ Mode::Stream { .. } => Mode::Stream {
+ pending: self.content.text(),
+ parsed: markdown::Content::parse(""),
+ },
+ }
}
}
Message::LinkClicked(link) => {
let _ = open::that_in_background(link.to_string());
}
+ Message::ToggleStream(enable_stream) => {
+ self.mode = if enable_stream {
+ Mode::Stream {
+ pending: self.content.text(),
+ parsed: markdown::Content::parse(""),
+ }
+ } else {
+ Mode::Oneshot(
+ markdown::parse(&self.content.text()).collect(),
+ )
+ };
+ }
+ Message::NextToken => match &mut self.mode {
+ Mode::Oneshot(_) => {}
+ Mode::Stream { pending, parsed } => {
+ if pending.is_empty() {
+ self.mode = Mode::Oneshot(parsed.items().to_vec());
+ } else {
+ let mut tokens = pending.split(' ');
+
+ if let Some(token) = tokens.next() {
+ parsed.push_str(&format!("{token} "));
+ }
+
+ *pending = tokens.collect::<Vec<_>>().join(" ");
+ }
+ }
+ },
}
}
@@ -63,20 +112,45 @@ impl Markdown {
.font(Font::MONOSPACE)
.highlight("markdown", highlighter::Theme::Base16Ocean);
+ let items = match &self.mode {
+ Mode::Oneshot(items) => items.as_slice(),
+ Mode::Stream { parsed, .. } => parsed.items(),
+ };
+
let preview = markdown(
- &self.items,
+ items,
markdown::Settings::default(),
markdown::Style::from_palette(self.theme.palette()),
)
.map(Message::LinkClicked);
- row![editor, scrollable(preview).spacing(10).height(Fill)]
- .spacing(10)
- .padding(10)
- .into()
+ row![
+ editor,
+ hover(
+ scrollable(preview).spacing(10).width(Fill).height(Fill),
+ right(
+ toggler(matches!(self.mode, Mode::Stream { .. }))
+ .label("Stream")
+ .on_toggle(Message::ToggleStream)
+ )
+ .padding([0, 20])
+ )
+ ]
+ .spacing(10)
+ .padding(10)
+ .into()
}
fn theme(&self) -> Theme {
self.theme.clone()
}
+
+ fn subscription(&self) -> Subscription<Message> {
+ match self.mode {
+ Mode::Oneshot(_) => Subscription::none(),
+ Mode::Stream { .. } => {
+ time::every(milliseconds(20)).map(|_| Message::NextToken)
+ }
+ }
+ }
}