pulldown_cmark_mdcat/terminal/capabilities/
iterm2.rs

1// Copyright 2018-2020 Sebastian Wiesner <sebastian@swsnr.de>
2
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Support for specific iTerm2 features.
8//!
9//! This module provides the iTerm2 marks and the iTerm2 image protocol.
10
11use std::borrow::Cow;
12use std::io::{self, Result, Write};
13
14use base64::engine::general_purpose::STANDARD;
15use base64::Engine;
16use tracing::{event, instrument, Level};
17
18use crate::resources::{svg, InlineImageProtocol};
19use crate::terminal::osc::write_osc;
20use crate::ResourceUrlHandler;
21
22/// Iterm2 terminal protocols.
23#[derive(Debug, Copy, Clone)]
24pub struct ITerm2Protocol;
25
26impl ITerm2Protocol {
27    /// Write an iterm2 mark command to the given `writer`.
28    pub fn set_mark<W: Write>(self, writer: &mut W) -> io::Result<()> {
29        write_osc(writer, "1337;SetMark")
30    }
31}
32
33/// The iterm2 inline image protocol.
34///
35/// See <https://iterm2.com/documentation-images.html> for details; effectively we write a base64
36/// encoded dump of the pixel data.
37///
38/// This implementation does **not** validate whether iterm2 actually supports the image type;
39/// it writes data opportunistically and hopes iTerm2 copes.  For rare formats which are not
40/// supported by macOS, this may yield false positives, i.e. this implementation might not return
41/// an error even though iTerm2 cannot actually display the image.
42impl InlineImageProtocol for ITerm2Protocol {
43    #[instrument(skip(self, writer, _terminal_size, resource_handler), fields(url = %url))]
44    fn write_inline_image(
45        &self,
46        writer: &mut dyn Write,
47        resource_handler: &dyn ResourceUrlHandler,
48        url: &url::Url,
49        _terminal_size: crate::TerminalSize,
50    ) -> Result<()> {
51        let mime_data = resource_handler.read_resource(url)?;
52        event!(
53            Level::DEBUG,
54            "Received data of mime type {:?}",
55            mime_data.mime_type
56        );
57
58        // Determine the local file name to use, by taking the last segment of the URL.
59        // If the URL has no last segment do not tell iterm about a file name.
60        let name = url
61            .path_segments()
62            .and_then(|s| s.last())
63            .map(Cow::Borrowed);
64        let (name, contents) = if let Some("image/svg+xml") = mime_data.mime_type_essence() {
65            event!(Level::DEBUG, "Rendering SVG from {}", url);
66            (
67                name.map(|n| {
68                    let mut name = String::new();
69                    name.push_str(&n);
70                    name.push_str(".png");
71                    Cow::Owned(name)
72                }),
73                Cow::Owned(svg::render_svg_to_png(&mime_data.data)?),
74            )
75        } else {
76            event!(Level::DEBUG, "Rendering mime data literally");
77            (name, Cow::Borrowed(&mime_data.data))
78        };
79        let data = STANDARD.encode(contents.as_ref());
80        write_osc(
81            writer,
82            &name.map_or_else(
83                || format!("1337;File=size={};inline=1:{}", contents.len(), data),
84                |name| {
85                    format!(
86                        "1337;File=name={};size={};inline=1:{}",
87                        STANDARD.encode(name.as_bytes()),
88                        contents.len(),
89                        data
90                    )
91                },
92            ),
93        )
94    }
95}