1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Utilities for [`Disposable`].
//!
//! [`Disposable`]: crate::xterm::Disposable

use super::{
    interface, object, Disposable, IntoJsInterface, Terminal, TerminalOptions,
};

use js_sys::{Function, Object};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast};

use core::ops::{Deref, DerefMut};

interface! {
    #[allow(clippy::module_name_repetitions)]
    pub trait XtermDisposable mirrors Disposable {
        /// Disposes of the instance.
        ///
        /// This can involve unregistering an event listener or cleaning up
        /// resources or anything else that should happen when an instance is
        /// disposed of.
        fn dispose(&self);
    }
}

/// A wrapper for [`Disposable`] that calls [`dispose`] on [`Drop`].
///
/// [`dispose`]: XtermDisposable::dispose
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(clippy::module_name_repetitions)]
pub struct DisposableWrapper<D: XtermDisposable> {
    /// The actual [`Disposable`] instance that's being wrapped.
    inner: Option<D>,
}

impl<D: XtermDisposable + Default> Default for DisposableWrapper<D> {
    fn default() -> Self {
        D::default().into()
    }
}

impl<D: XtermDisposable> DisposableWrapper<D> {
    /// Pulls the inner [`XtermDisposable`] implementation (`D`) out of the
    /// wrapper, making it so that [`dispose`] is not called on [`Drop`].
    ///
    /// [`dispose`]: XtermDisposable::dispose
    pub fn manually_dispose(mut self) -> D {
        // Every method we offer (other than the Drop impl) assume that inner
        // will be `Some`.
        //
        // This is fairly easy to show; the only constructor we offer
        // initializes `inner` as `Some` and this is the only method that goes
        // from `Some` to `None`. Because this method takes the wrapper by value
        // (consumes it), this is okay; none of the methods (other than `drop`)
        // can be called on this wrapper after this method is called.
        self.inner.take().unwrap()
    }
}

impl<D: XtermDisposable> From<D> for DisposableWrapper<D> {
    fn from(inner: D) -> Self {
        Self { inner: Some(inner) }
    }
}

impl<D: XtermDisposable> Deref for DisposableWrapper<D> {
    type Target = D;

    fn deref(&self) -> &D {
        self.inner.as_ref().unwrap()
    }
}

impl<D: XtermDisposable> DerefMut for DisposableWrapper<D> {
    fn deref_mut(&mut self) -> &mut D {
        self.inner.as_mut().unwrap()
    }
}

impl<D: XtermDisposable> Drop for DisposableWrapper<D> {
    fn drop(&mut self) {
        if let Some(ref inner) = self.inner {
            inner.dispose();
        }
    }
}

/// A type that satisfies the [`Disposable`] interface and does nothing on
/// `dispose`.
///
/// Can be used wherever an `IDisposable` is required.
///
/// [`Disposable`]: crate::xterm::Disposable
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct NoOpDispose {
    /// JavaScript object that just has a no-op `dispose` function.
    obj: Object,
}

impl Default for NoOpDispose {
    fn default() -> Self {
        Self::new()
    }
}

impl NoOpDispose {
    /// Constructs a new [`NoOpDispose`].
    #[must_use]
    pub fn new() -> Self {
        Self {
            obj: object! { dispose: Function::new_no_args("return;") },
        }
    }
}

// This makes it so that we get an `XtermDisposable` and `IntoJsInterface` impl.
impl AsRef<Disposable> for NoOpDispose {
    fn as_ref(&self) -> &Disposable {
        JsCast::unchecked_ref(&self.obj)
    }
}

impl Deref for NoOpDispose {
    type Target = Disposable;

    fn deref(&self) -> &Disposable {
        self.as_ref()
    }
}

impl Terminal {
    /// [`Terminal`] constructor that encloses the resulting [`Terminal`] in a
    /// [`DisposableWrapper`].
    ///
    /// This is otherwise identical to [`Terminal::new`].
    #[must_use]
    pub fn new_with_wrapper(
        options: Option<TerminalOptions>,
    ) -> DisposableWrapper<Terminal> {
        Self::new(options).into()
    }
}