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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
//!
//! # iTerm2
//! A Rust crate to allow easy access to the various escape codes in iTerm2.
//!
//! # Usage
//!
//! ```rust
//! extern crate iterm2;
//! use iterm2::*;
//!
//! clear_scrollback().unwrap();
//! anchor("https://google.com", "google").unwrap();
//! attention(AttentionType::Firework).unwrap();
//! ```
//!

extern crate base64;

use base64::encode;
use std::io::stdout;
use std::io::Write;

pub type TerminalError = Result<(), std::io::Error>;

/// The possible cusor shpaes
#[derive(Debug, Clone, Copy)]
pub enum CursorShape {
    /// A solid vertical block
    Block,
    /// A thin vertical line
    VerticalBar,
    /// A thin horizonal line
    Underline,
}

/// The possible types of attention actions
#[derive(Debug, Clone, Copy)]
pub enum AttentionType {
    /// Start visual display
    Yes,
    /// Stop visual display
    No,
    /// Show fireworks
    Firework,
}

/// Display a clickable link with custom display text
pub fn anchor(url: &str, display_text: &str) -> TerminalError {
    stdout().write_all(format!("\x1b]8;;{}\x07{}\x1b]8;;\x07", url, display_text).as_bytes())
}

/// Set the shape of the cursor
pub fn set_cursor_shape(shape: CursorShape) -> TerminalError {
    use CursorShape::*;
    let shape_val = match shape {
        Block => 0,
        VerticalBar => 1,
        Underline => 2,
    };
    stdout().write_all(format!("\x1b]1337;CursorShape={}\x07", shape_val).as_bytes())
}

/// Set a mark at the current line
pub fn set_mark() -> TerminalError {
    stdout().write_all(b"\x1b]1337;SetMark\x07")
}

/// Attempt to make iTerm the focused application
pub fn steal_focus() -> TerminalError {
    stdout().write_all(b"\x1b]1337;StealFocus\x07")
}

/// Clear the terminals scroll history
pub fn clear_scrollback() -> TerminalError {
    stdout().write_all(b"\x1b]1337;ClearScrollback\x07")
}

/// Sets the terminals current working directory
pub fn set_current_dir(dir: &str) -> TerminalError {
    stdout().write_all(format!("\x1b]1337;CurrentDir={}\x07", dir).as_bytes())
}

/// Send a system wide Growl notification
pub fn send_notification(message: &str) -> TerminalError {
    stdout().write_all(format!("\x1b]9;{}\x07", message).as_bytes())
}

/// Sets the clipboard
// TODO: Add support for the other clipboards
pub fn set_clipboard(text: &str) -> TerminalError {
    stdout().write_all(b"\x1b]1337;CopyToClipboard=\x07")?;
    stdout().write_all(text.as_bytes())?;
    stdout().write_all(b"\n\x1b]1337;EndCopy\x07")
}

/// Sets the tab colors to a custom rgb value
pub fn set_tab_colors(red: u8, green: u8, blue: u8) -> TerminalError {
    stdout().write_all(format!("\x1b]6;1;bg;red;brightness;{}\x07", red).as_bytes())?;
    stdout().write_all(format!("\x1b]6;1;bg;green;brightness;{}\x07", green).as_bytes())?;
    stdout().write_all(format!("\x1b]6;1;bg;blue;brightness;{}\x07", blue).as_bytes())
}

/// Restore the tab colors to defaults
pub fn restore_tab_colors() -> TerminalError {
    stdout().write_all(b"\x1b]6;1;bg;*;default\x07")
}

/// Sets the terminal color palette
///
/// For details on the format, see "Change the color palette" at https://www.iterm2.com/documentation-escape-codes.html
// TODO: Add better parameters
pub fn set_color_palette(colors: &str) -> TerminalError {
    stdout().write_all(format!("\x1b]1337;SetColors={}\x07", colors).as_bytes())
}

/// A builder for terminal annotations
pub struct Annotation {
    message: String,
    length: Option<usize>,
    xcoord: Option<usize>,
    ycoord: Option<usize>,
    hidden: bool,
}

impl Annotation {
    /// Create a new annotation with given text
    pub fn new(message: &str) -> Annotation {
        Annotation {
            message: message.to_owned(),
            length: None,
            xcoord: None,
            ycoord: None,
            hidden: false,
        }
    }

    /// Set the length of the annotation
    pub fn length(&mut self, length: usize) -> &mut Annotation {
        self.length = Some(length);
        self
    }

    /// Set the (x,y) coordinates of the annotation
    pub fn coords(&mut self, x: usize, y: usize) -> &mut Annotation {
        self.xcoord = Some(x);
        self.ycoord = Some(y);
        self
    }

    /// Set the annotation to be hidden
    pub fn hidden(&mut self, hide: bool) -> &mut Annotation {
        self.hidden = hide;
        self
    }

    /// Display the annotation
    pub fn show(&self) -> TerminalError {
        let value = match self {
            Annotation {
                message: msg,
                length: None,
                xcoord: None,
                ycoord: None,
                ..
            } => msg.to_owned(),
            Annotation {
                message: msg,
                length: Some(len),
                xcoord: None,
                ycoord: None,
                ..
            } => format!("{}|{}", len, msg),
            Annotation {
                message: msg,
                length: Some(len),
                xcoord: Some(x),
                ycoord: Some(y),
                ..
            } => format!("{}|{}|{}|{}", msg, len, x, y),
            _ => panic!("Invalid parameters"), //TODO: Convert to custom error
        };
        let key = if self.hidden {
            "AddHiddenAnnotation"
        } else {
            "AddAnnotation"
        };
        stdout().write_all(format!("\x1b]1337;{}={}\x07", key, value).as_bytes())
    }
}

/// Set the visibility of the cursor guide
pub fn cursor_guide(show: bool) -> TerminalError {
    let value = if show { "yes" } else { "no" };
    stdout().write_all(format!("\x1b]1337;HighlightCursorLine={}\x07", value).as_bytes())
}

/// Trigger a dock bounce notification or fireworks
pub fn attention(kind: AttentionType) -> TerminalError {
    use AttentionType::*;
    let value = match kind {
        Yes => "yes",
        No => "no",
        Firework => "fireworks",
    };
    stdout().write_all(format!("\x1b]1337;RequestAttention={}\x07", value).as_bytes())
}

/// Set the terminal background to the image at a path
pub fn set_background_image(filename: &str) -> TerminalError {
    let base64_filename = encode(filename.as_bytes());
    stdout()
        .write_all(format!("\x1b]1337;SetBackgroundImageFile={}\x07", base64_filename).as_bytes())
}

/// Gets the size of a cell in points as a floating point number
///
/// *Not yet implemented*
//TODO: Implement
#[allow(unused_variables)]
pub fn get_cell_size(filename: &str) -> Result<(f32, f32), std::io::Error> {
    unimplemented!()
}

/// Gets the value of a session variable
///
/// *Not yet implemented*
//TODO: Implement
#[allow(unused_variables)]
pub fn get_terminal_variable(filename: &str) -> Result<String, std::io::Error> {
    unimplemented!()
}

/// Download a file. Accepts raw file contents and option arguments
///
/// See the [iTerm2 docs](https://www.iterm2.com/documentation-images.html) for more information
pub fn download_file(args: &[(&str, &str)], img_data: &[u8]) -> TerminalError {
    let joined_args = args
        .iter()
        .map(|item| format!("{}={}", item.0, item.1))
        .collect::<Vec<_>>()
        .join(";");
    stdout().write_all(format!("\x1b]1337;File={}:", joined_args).as_bytes())?;

    let encoded_data = base64::encode(img_data);
    stdout().write_all(&encoded_data.as_bytes())?;
    stdout().write_all(b"\x07")
}

/// Configures touchbar key lables
///
/// Seethe [iTerm2 docs](https://www.iterm2.com/documentation-escape-codes.html) for more information
pub fn set_touchbar_key_label(key: &str, value: &str) -> TerminalError {
    stdout().write_all(format!("\x1b]1337;SetKeyLabel={}={}\x07", key, value).as_bytes())
}

/// Push the current key labels
pub fn push_current_touchbar_labels() -> TerminalError {
    stdout().write_all(b"\x1b]1337;PushKeyLabels\x07")
}

/// Pop the current key labels
pub fn pop_current_touchbar_labels() -> TerminalError {
    stdout().write_all(b"\x1b]1337;PopKeyLabels\x07")
}

/// Push a specific touchbar key label by name
pub fn push_touchbar_label(label: &str) -> TerminalError {
    stdout().write_all(format!("\x1b1337;PushKeyLabels={}\x07", label).as_bytes())
}

/// Pop a specific touchbar key label by name
pub fn pop_touchbar_label(label: &str) -> TerminalError {
    stdout().write_all(format!("\x1b1337;PopKeyLabels={}\x07", label).as_bytes())
}

/// Sets the terminals unicode version
pub fn set_unicode_version(version: u8) -> TerminalError {
    stdout().write_all(format!("\x1b1337;UnicodeVersion={}\x07", version).as_bytes())
}