Building a text editor in ICED

I am trying to build a collaborative text editor in rust. This editor communicates to other editor via websockets. I have not done the collaboration part yet but I am struggling to first establish the communication via the websockets first

use iced::futures::channel::mpsc;
use iced::futures::{SinkExt, Stream, StreamExt};
use iced::widget::text_editor;
use iced::widget::text_editor::{Action, Content};
use iced::{stream, Element, Subscription, Task};
use std::str::FromStr;
use tokio::net::TcpListener;

#[derive(Debug,Default)]
pub struct Editor{
    content : Content,
    sender : Option<mpsc::Sender<String>>
}

#[derive(Clone,Debug)]
pub enum Message{
    Edit(Action),
    Event(Event),
    None
}

#[derive(Debug,Clone)]
pub enum Event{
    Sender(mpsc::Sender<String>),
    WorkDone
}
impl Editor{
    pub fn new() -> Self{

        Editor {
            content: Content::new(),
            sender: None
        }
    }
    
    pub fn view(&self) -> Element<'_,Message>{
       text_editor(&self.content)
           .placeholder("Here goes test")
           .on_action(Message::Edit)
           .into()
    }
    
    pub fn subscription(&self) -> Subscription<Message>{
        Subscription::run(send_something).map(Message::Event)
    }
    
    pub fn update(&mut self, msg : Message){
        match msg{
           Message::Edit(a) => {
               self.content.perform(a);
          if let Some(mut x) = self.sender.clone(){
               let _ = Task::perform(
                   async move {
                       x.send(String::from_str("some x").unwrap()).await.unwrap();
                   }
               ,|_|Message::None); 
           };
           }, 
            Message::Event(e) => {
                match e{
                   Event::Sender(s) => {
                       self.sender = Some(s);
                   },
                    Event::WorkDone => {}
                }
            }
            _ => ()
        }
    }
    
}


pub fn send_something() -> impl Stream<Item=Event> {
    stream::channel(100,|mut output| async move{
        let tcp_listener = TcpListener::bind("127.0.0.1:9001").await.unwrap();
        if let Ok((stream,addr)) = tcp_listener.accept().await{
           tokio::spawn(async move{
                let ws_stream = tokio_tungstenite::accept_async(stream).await.unwrap();  
               let (sender, mut receiver) = mpsc::channel(100);
               let _ = &mut output.send(Event::Sender(sender)).await.unwrap();
               let (mut out, mut inc) = ws_stream.split();
               while let Some(Ok(x)) = inc.next().await {
                 let txt = x.into_text().unwrap();
                   println!("{:?}",txt);
                   while let Some(msg) = receiver.next().await{
                      out.send(tungstenite::Message::text(msg)).await.unwrap(); 
                   }
               }
               
           });
        }
    })
}


This is the code currently my goal is to open a websocket server and broadcast what I am typing in my ICED editor in the server. I am trying to use ICED subscription to do this but this is not working as expected. Any help would be appreciated.

Iced has an example for websocket explicitly, but the current version is using iced::task::sipper(), which isn't available yet: iced/examples/websocket at master · iced-rs/iced · GitHub

The last released version is a lot closer to yours: iced/examples/websocket/src/echo.rs at 0.13 · iced-rs/iced · GitHub

The trick seems to be that you need to need to "spawn" in iced's Task system, by shuffling channels around in your messages, but I also had a lot of trouble with this in my last attempt with iced. Good luck!

My solution won't work for you. However, since the question is coming up, I share my approach which probably someone will find useful. I use websocket for a terminal communication in my web application. My terminal is a simple command line application which is picked by web server and shared with web page using websocket protocol. Why I like this approach? When I develop a terminal endpoint I need to know nothing about websocket, HTTP, and other stuff. It's just simple in and out pipes.

From a first glance it seems that these nested loops contain a logic bug:

             while let Some(Ok(x)) = inc.next().await {
               let txt = x.into_text().unwrap();
                 println!("{:?}",txt);
                 while let Some(msg) = receiver.next().await{
                    out.send(tungstenite::Message::text(msg)).await.unwrap(); 
                 }
             }

I'm guessing here, but I think inc is for incoming messages and receiver is the channels Receiver for getting messages from the UI. If that is the case, then you only will receive messages from the UI after you received an incoming message on the server. And even worse, after the first incoming messages it won't process other incoming messages anymore.

One solution would be to use tokio::select! to process incoming messages and UI events concurrently. Something like the following

loop {
  select! {
      Some(Ok(x)) = inc.next() => {
        // process incoming message
      },
      Some(msg) = receiver.next() => {
        out.send(...).await().unwrap();
      }
   }
}

I wrote the code out of my head, so it might not be 100% right, but you likely will get the idea.

BTW: Feel free to join the iced Discord channel, there is a bunch of very helpful people over there.