Detailed changes
@@ -15526,6 +15526,18 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
+[[package]]
+name = "svg_preview"
+version = "0.1.0"
+dependencies = [
+ "editor",
+ "file_icons",
+ "gpui",
+ "ui",
+ "workspace",
+ "workspace-hack",
+]
+
[[package]]
name = "svgtypes"
version = "0.15.3"
@@ -20021,6 +20033,7 @@ dependencies = [
"snippet_provider",
"snippets_ui",
"supermaven",
+ "svg_preview",
"sysinfo",
"tab_switcher",
"task",
@@ -95,6 +95,7 @@ members = [
"crates/markdown_preview",
"crates/media",
"crates/menu",
+ "crates/svg_preview",
"crates/migrator",
"crates/mistral",
"crates/multi_buffer",
@@ -304,6 +305,7 @@ lmstudio = { path = "crates/lmstudio" }
lsp = { path = "crates/lsp" }
markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
+svg_preview = { path = "crates/svg_preview" }
media = { path = "crates/media" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
@@ -491,13 +491,27 @@
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
"ctrl-\\": "pane::SplitRight",
- "ctrl-k v": "markdown::OpenPreviewToTheSide",
- "ctrl-shift-v": "markdown::OpenPreview",
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
"alt-.": "editor::GoToHunk",
"alt-,": "editor::GoToPreviousHunk"
}
},
+ {
+ "context": "Editor && extension == md",
+ "use_key_equivalents": true,
+ "bindings": {
+ "ctrl-k v": "markdown::OpenPreviewToTheSide",
+ "ctrl-shift-v": "markdown::OpenPreview"
+ }
+ },
+ {
+ "context": "Editor && extension == svg",
+ "use_key_equivalents": true,
+ "bindings": {
+ "ctrl-k v": "svg::OpenPreviewToTheSide",
+ "ctrl-shift-v": "svg::OpenPreview"
+ }
+ },
{
"context": "Editor && mode == full",
"bindings": {
@@ -545,11 +545,25 @@
"cmd-k r": "editor::RevealInFileManager",
"cmd-k p": "editor::CopyPath",
"cmd-\\": "pane::SplitRight",
- "cmd-k v": "markdown::OpenPreviewToTheSide",
- "cmd-shift-v": "markdown::OpenPreview",
"ctrl-cmd-c": "editor::DisplayCursorNames"
}
},
+ {
+ "context": "Editor && extension == md",
+ "use_key_equivalents": true,
+ "bindings": {
+ "cmd-k v": "markdown::OpenPreviewToTheSide",
+ "cmd-shift-v": "markdown::OpenPreview"
+ }
+ },
+ {
+ "context": "Editor && extension == svg",
+ "use_key_equivalents": true,
+ "bindings": {
+ "cmd-k v": "svg::OpenPreviewToTheSide",
+ "cmd-shift-v": "svg::OpenPreview"
+ }
+ },
{
"context": "Editor && mode == full",
"use_key_equivalents": true,
@@ -1,7 +1,7 @@
use auto_update::AutoUpdater;
use client::proto::UpdateNotification;
use editor::{Editor, MultiBuffer};
-use gpui::{App, Context, DismissEvent, Entity, SharedString, Window, actions, prelude::*};
+use gpui::{App, Context, DismissEvent, Entity, Window, actions, prelude::*};
use http_client::HttpClient;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use release_channel::{AppVersion, ReleaseChannel};
@@ -94,7 +94,6 @@ fn view_release_notes_locally(
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
- let tab_content = Some(SharedString::from(body.title.to_string()));
let editor = cx.new(|cx| {
Editor::for_multibuffer(buffer, Some(project), window, cx)
});
@@ -105,7 +104,6 @@ fn view_release_notes_locally(
editor,
workspace_handle,
language_registry,
- tab_content,
window,
cx,
);
@@ -17,10 +17,9 @@ use ui::prelude::*;
use workspace::item::{Item, ItemHandle};
use workspace::{Pane, Workspace};
-use crate::OpenPreviewToTheSide;
use crate::markdown_elements::ParsedMarkdownElement;
use crate::{
- OpenFollowingPreview, OpenPreview,
+ OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide,
markdown_elements::ParsedMarkdown,
markdown_parser::parse_markdown,
markdown_renderer::{RenderContext, render_markdown_block},
@@ -36,7 +35,6 @@ pub struct MarkdownPreviewView {
contents: Option<ParsedMarkdown>,
selected_block: usize,
list_state: ListState,
- tab_content_text: Option<SharedString>,
language_registry: Arc<LanguageRegistry>,
parsing_markdown_task: Option<Task<Result<()>>>,
mode: MarkdownPreviewMode,
@@ -173,7 +171,6 @@ impl MarkdownPreviewView {
editor,
workspace_handle,
language_registry,
- None,
window,
cx,
)
@@ -192,7 +189,6 @@ impl MarkdownPreviewView {
editor,
workspace_handle,
language_registry,
- None,
window,
cx,
)
@@ -203,7 +199,6 @@ impl MarkdownPreviewView {
active_editor: Entity<Editor>,
workspace: WeakEntity<Workspace>,
language_registry: Arc<LanguageRegistry>,
- tab_content_text: Option<SharedString>,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
@@ -324,7 +319,6 @@ impl MarkdownPreviewView {
workspace: workspace.clone(),
contents: None,
list_state,
- tab_content_text,
language_registry,
parsing_markdown_task: None,
image_cache: RetainAllImageCache::new(cx),
@@ -405,12 +399,6 @@ impl MarkdownPreviewView {
},
);
- let tab_content = editor.read(cx).tab_content_text(0, cx);
-
- if self.tab_content_text.is_none() {
- self.tab_content_text = Some(format!("Preview {}", tab_content).into());
- }
-
self.active_editor = Some(EditorState {
editor,
_subscription: subscription,
@@ -547,21 +535,28 @@ impl Focusable for MarkdownPreviewView {
}
}
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum PreviewEvent {}
-
-impl EventEmitter<PreviewEvent> for MarkdownPreviewView {}
+impl EventEmitter<()> for MarkdownPreviewView {}
impl Item for MarkdownPreviewView {
- type Event = PreviewEvent;
+ type Event = ();
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
Some(Icon::new(IconName::FileDoc))
}
- fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
- self.tab_content_text
- .clone()
+ fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
+ self.active_editor
+ .as_ref()
+ .and_then(|editor_state| {
+ let buffer = editor_state.editor.read(cx).buffer().read(cx);
+ let buffer = buffer.as_singleton()?;
+ let file = buffer.read(cx).file()?;
+ let local_file = file.as_local()?;
+ local_file
+ .abs_path(cx)
+ .file_name()
+ .map(|name| format!("Preview {}", name.to_string_lossy()).into())
+ })
.unwrap_or_else(|| SharedString::from("Markdown Preview"))
}
@@ -0,0 +1,20 @@
+[package]
+name = "svg_preview"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/svg_preview.rs"
+
+[dependencies]
+editor.workspace = true
+file_icons.workspace = true
+gpui.workspace = true
+ui.workspace = true
+workspace.workspace = true
+workspace-hack.workspace = true
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,19 @@
+use gpui::{App, actions};
+use workspace::Workspace;
+
+pub mod svg_preview_view;
+
+actions!(
+ svg,
+ [OpenPreview, OpenPreviewToTheSide, OpenFollowingPreview]
+);
+
+pub fn init(cx: &mut App) {
+ cx.observe_new(|workspace: &mut Workspace, window, cx| {
+ let Some(window) = window else {
+ return;
+ };
+ crate::svg_preview_view::SvgPreviewView::register(workspace, window, cx);
+ })
+ .detach();
+}
@@ -0,0 +1,323 @@
+use std::path::PathBuf;
+
+use editor::{Editor, EditorEvent};
+use file_icons::FileIcons;
+use gpui::{
+ App, Context, Entity, EventEmitter, FocusHandle, Focusable, ImageSource, IntoElement,
+ ParentElement, Render, Resource, RetainAllImageCache, Styled, Subscription, WeakEntity, Window,
+ div, img,
+};
+use ui::prelude::*;
+use workspace::item::Item;
+use workspace::{Pane, Workspace};
+
+use crate::{OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide};
+
+pub struct SvgPreviewView {
+ focus_handle: FocusHandle,
+ svg_path: Option<PathBuf>,
+ image_cache: Entity<RetainAllImageCache>,
+ _editor_subscription: Subscription,
+ _workspace_subscription: Option<Subscription>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum SvgPreviewMode {
+ /// The preview will always show the contents of the provided editor.
+ Default,
+ /// The preview will "follow" the last active editor of an SVG file.
+ Follow,
+}
+
+impl SvgPreviewView {
+ pub fn register(workspace: &mut Workspace, _window: &mut Window, _cx: &mut Context<Workspace>) {
+ workspace.register_action(move |workspace, _: &OpenPreview, window, cx| {
+ if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) {
+ if Self::is_svg_file(&editor, cx) {
+ let view = Self::create_svg_view(
+ SvgPreviewMode::Default,
+ workspace,
+ editor.clone(),
+ window,
+ cx,
+ );
+ workspace.active_pane().update(cx, |pane, cx| {
+ if let Some(existing_view_idx) =
+ Self::find_existing_preview_item_idx(pane, &editor, cx)
+ {
+ pane.activate_item(existing_view_idx, true, true, window, cx);
+ } else {
+ pane.add_item(Box::new(view), true, true, None, window, cx)
+ }
+ });
+ cx.notify();
+ }
+ }
+ });
+
+ workspace.register_action(move |workspace, _: &OpenPreviewToTheSide, window, cx| {
+ if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) {
+ if Self::is_svg_file(&editor, cx) {
+ let editor_clone = editor.clone();
+ let view = Self::create_svg_view(
+ SvgPreviewMode::Default,
+ workspace,
+ editor_clone,
+ window,
+ cx,
+ );
+ let pane = workspace
+ .find_pane_in_direction(workspace::SplitDirection::Right, cx)
+ .unwrap_or_else(|| {
+ workspace.split_pane(
+ workspace.active_pane().clone(),
+ workspace::SplitDirection::Right,
+ window,
+ cx,
+ )
+ });
+ pane.update(cx, |pane, cx| {
+ if let Some(existing_view_idx) =
+ Self::find_existing_preview_item_idx(pane, &editor, cx)
+ {
+ pane.activate_item(existing_view_idx, true, true, window, cx);
+ } else {
+ pane.add_item(Box::new(view), false, false, None, window, cx)
+ }
+ });
+ cx.notify();
+ }
+ }
+ });
+
+ workspace.register_action(move |workspace, _: &OpenFollowingPreview, window, cx| {
+ if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) {
+ if Self::is_svg_file(&editor, cx) {
+ let view = Self::create_svg_view(
+ SvgPreviewMode::Follow,
+ workspace,
+ editor,
+ window,
+ cx,
+ );
+ workspace.active_pane().update(cx, |pane, cx| {
+ pane.add_item(Box::new(view), true, true, None, window, cx)
+ });
+ cx.notify();
+ }
+ }
+ });
+ }
+
+ fn find_existing_preview_item_idx(
+ pane: &Pane,
+ editor: &Entity<Editor>,
+ cx: &App,
+ ) -> Option<usize> {
+ let editor_path = Self::get_svg_path(editor, cx);
+ pane.items_of_type::<SvgPreviewView>()
+ .find(|view| {
+ let view_read = view.read(cx);
+ view_read.svg_path.is_some() && view_read.svg_path == editor_path
+ })
+ .and_then(|view| pane.index_for_item(&view))
+ }
+
+ pub fn resolve_active_item_as_svg_editor(
+ workspace: &Workspace,
+ cx: &mut Context<Workspace>,
+ ) -> Option<Entity<Editor>> {
+ let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
+
+ if Self::is_svg_file(&editor, cx) {
+ Some(editor)
+ } else {
+ None
+ }
+ }
+
+ fn create_svg_view(
+ mode: SvgPreviewMode,
+ workspace: &mut Workspace,
+ editor: Entity<Editor>,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+ ) -> Entity<SvgPreviewView> {
+ let workspace_handle = workspace.weak_handle();
+ SvgPreviewView::new(mode, editor, workspace_handle, window, cx)
+ }
+
+ pub fn new(
+ mode: SvgPreviewMode,
+ active_editor: Entity<Editor>,
+ workspace_handle: WeakEntity<Workspace>,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+ ) -> Entity<Self> {
+ cx.new(|cx| {
+ let svg_path = Self::get_svg_path(&active_editor, cx);
+ let image_cache = RetainAllImageCache::new(cx);
+
+ let subscription = cx.subscribe_in(
+ &active_editor,
+ window,
+ |this: &mut SvgPreviewView, _editor, event: &EditorEvent, window, cx| {
+ match event {
+ EditorEvent::Saved => {
+ // Remove cached image to force reload
+ if let Some(svg_path) = &this.svg_path {
+ let resource = Resource::Path(svg_path.clone().into());
+ this.image_cache.update(cx, |cache, cx| {
+ cache.remove(&resource, window, cx);
+ });
+ }
+ cx.notify();
+ }
+ _ => {}
+ }
+ },
+ );
+
+ // Subscribe to workspace active item changes to follow SVG files
+ let workspace_subscription = if mode == SvgPreviewMode::Follow {
+ workspace_handle.upgrade().map(|workspace_handle| {
+ cx.subscribe_in(
+ &workspace_handle,
+ window,
+ |this: &mut SvgPreviewView,
+ workspace,
+ event: &workspace::Event,
+ _window,
+ cx| {
+ match event {
+ workspace::Event::ActiveItemChanged => {
+ let workspace_read = workspace.read(cx);
+ if let Some(active_item) = workspace_read.active_item(cx) {
+ if let Some(editor_entity) =
+ active_item.downcast::<Editor>()
+ {
+ if Self::is_svg_file(&editor_entity, cx) {
+ let new_path =
+ Self::get_svg_path(&editor_entity, cx);
+ if this.svg_path != new_path {
+ this.svg_path = new_path;
+ cx.notify();
+ }
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+ },
+ )
+ })
+ } else {
+ None
+ };
+
+ Self {
+ focus_handle: cx.focus_handle(),
+ svg_path,
+ image_cache,
+ _editor_subscription: subscription,
+ _workspace_subscription: workspace_subscription,
+ }
+ })
+ }
+
+ pub fn is_svg_file<C>(editor: &Entity<Editor>, cx: &C) -> bool
+ where
+ C: std::borrow::Borrow<App>,
+ {
+ let app = cx.borrow();
+ let buffer = editor.read(app).buffer().read(app);
+ if let Some(buffer) = buffer.as_singleton() {
+ if let Some(file) = buffer.read(app).file() {
+ return file
+ .path()
+ .extension()
+ .and_then(|ext| ext.to_str())
+ .map(|ext| ext.eq_ignore_ascii_case("svg"))
+ .unwrap_or(false);
+ }
+ }
+ false
+ }
+
+ fn get_svg_path<C>(editor: &Entity<Editor>, cx: &C) -> Option<PathBuf>
+ where
+ C: std::borrow::Borrow<App>,
+ {
+ let app = cx.borrow();
+ let buffer = editor.read(app).buffer().read(app).as_singleton()?;
+ let file = buffer.read(app).file()?;
+ let local_file = file.as_local()?;
+ Some(local_file.abs_path(app))
+ }
+}
+
+impl Render for SvgPreviewView {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ v_flex()
+ .id("SvgPreview")
+ .key_context("SvgPreview")
+ .track_focus(&self.focus_handle(cx))
+ .size_full()
+ .bg(cx.theme().colors().editor_background)
+ .flex()
+ .justify_center()
+ .items_center()
+ .child(if let Some(svg_path) = &self.svg_path {
+ img(ImageSource::from(svg_path.clone()))
+ .image_cache(&self.image_cache)
+ .max_w_full()
+ .max_h_full()
+ .with_fallback(|| {
+ div()
+ .p_4()
+ .child("Failed to load SVG file")
+ .into_any_element()
+ })
+ .into_any_element()
+ } else {
+ div().p_4().child("No SVG file selected").into_any_element()
+ })
+ }
+}
+
+impl Focusable for SvgPreviewView {
+ fn focus_handle(&self, _cx: &App) -> FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl EventEmitter<()> for SvgPreviewView {}
+
+impl Item for SvgPreviewView {
+ type Event = ();
+
+ fn tab_icon(&self, _window: &Window, cx: &App) -> Option<Icon> {
+ // Use the same icon as SVG files in the file tree
+ self.svg_path
+ .as_ref()
+ .and_then(|svg_path| FileIcons::get_icon(svg_path, cx))
+ .map(Icon::from_path)
+ .or_else(|| Some(Icon::new(IconName::Image)))
+ }
+
+ fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
+ self.svg_path
+ .as_ref()
+ .and_then(|svg_path| svg_path.file_name())
+ .map(|name| name.to_string_lossy())
+ .map(|name| format!("Preview {}", name).into())
+ .unwrap_or_else(|| "SVG Preview".into())
+ }
+
+ fn telemetry_event_text(&self) -> Option<&'static str> {
+ Some("svg preview: open")
+ }
+
+ fn to_item_events(_event: &Self::Event, _f: impl FnMut(workspace::item::ItemEvent)) {}
+}
@@ -85,6 +85,7 @@ libc.workspace = true
log.workspace = true
markdown.workspace = true
markdown_preview.workspace = true
+svg_preview.workspace = true
menu.workspace = true
migrator.workspace = true
mimalloc = { version = "0.1", optional = true }
@@ -582,6 +582,7 @@ pub fn main() {
jj_ui::init(cx);
feedback::init(cx);
markdown_preview::init(cx);
+ svg_preview::init(cx);
welcome::init(cx);
settings_ui::init(cx);
extensions_ui::init(cx);
@@ -4323,6 +4323,7 @@ mod tests {
"search",
"snippets",
"supermaven",
+ "svg",
"tab_switcher",
"task",
"terminal",
@@ -1,5 +1,6 @@
-mod markdown_preview;
+mod preview;
mod repl_menu;
+
use agent_settings::AgentSettings;
use editor::actions::{
AddSelectionAbove, AddSelectionBelow, CodeActionSource, DuplicateLineDown, GoToDiagnostic,
@@ -571,7 +572,7 @@ impl Render for QuickActionBar {
.id("quick action bar")
.gap(DynamicSpacing::Base01.rems(cx))
.children(self.render_repl_menu(cx))
- .children(self.render_toggle_markdown_preview(self.workspace.clone(), cx))
+ .children(self.render_preview_button(self.workspace.clone(), cx))
.children(search_button)
.when(
AgentSettings::get_global(cx).enabled && AgentSettings::get_global(cx).button,
@@ -1,63 +0,0 @@
-use gpui::{AnyElement, Modifiers, WeakEntity};
-use markdown_preview::{
- OpenPreview, OpenPreviewToTheSide, markdown_preview_view::MarkdownPreviewView,
-};
-use ui::{IconButtonShape, Tooltip, prelude::*, text_for_keystroke};
-use workspace::Workspace;
-
-use super::QuickActionBar;
-
-impl QuickActionBar {
- pub fn render_toggle_markdown_preview(
- &self,
- workspace: WeakEntity<Workspace>,
- cx: &mut Context<Self>,
- ) -> Option<AnyElement> {
- let mut active_editor_is_markdown = false;
-
- if let Some(workspace) = self.workspace.upgrade() {
- workspace.update(cx, |workspace, cx| {
- active_editor_is_markdown =
- MarkdownPreviewView::resolve_active_item_as_markdown_editor(workspace, cx)
- .is_some();
- });
- }
-
- if !active_editor_is_markdown {
- return None;
- }
-
- let alt_click = gpui::Keystroke {
- key: "click".into(),
- modifiers: Modifiers::alt(),
- ..Default::default()
- };
-
- let button = IconButton::new("toggle-markdown-preview", IconName::Eye)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .style(ButtonStyle::Subtle)
- .tooltip(move |window, cx| {
- Tooltip::with_meta(
- "Preview Markdown",
- Some(&markdown_preview::OpenPreview),
- format!("{} to open in a split", text_for_keystroke(&alt_click, cx)),
- window,
- cx,
- )
- })
- .on_click(move |_, window, cx| {
- if let Some(workspace) = workspace.upgrade() {
- workspace.update(cx, |_, cx| {
- if window.modifiers().alt {
- window.dispatch_action(Box::new(OpenPreviewToTheSide), cx);
- } else {
- window.dispatch_action(Box::new(OpenPreview), cx);
- }
- });
- }
- });
-
- Some(button.into_any_element())
- }
-}
@@ -0,0 +1,95 @@
+use gpui::{AnyElement, Modifiers, WeakEntity};
+use markdown_preview::{
+ OpenPreview as MarkdownOpenPreview, OpenPreviewToTheSide as MarkdownOpenPreviewToTheSide,
+ markdown_preview_view::MarkdownPreviewView,
+};
+use svg_preview::{
+ OpenPreview as SvgOpenPreview, OpenPreviewToTheSide as SvgOpenPreviewToTheSide,
+ svg_preview_view::SvgPreviewView,
+};
+use ui::{IconButtonShape, Tooltip, prelude::*, text_for_keystroke};
+use workspace::Workspace;
+
+use super::QuickActionBar;
+
+#[derive(Clone, Copy)]
+enum PreviewType {
+ Markdown,
+ Svg,
+}
+
+impl QuickActionBar {
+ pub fn render_preview_button(
+ &self,
+ workspace_handle: WeakEntity<Workspace>,
+ cx: &mut Context<Self>,
+ ) -> Option<AnyElement> {
+ let mut preview_type = None;
+
+ if let Some(workspace) = self.workspace.upgrade() {
+ workspace.update(cx, |workspace, cx| {
+ if MarkdownPreviewView::resolve_active_item_as_markdown_editor(workspace, cx)
+ .is_some()
+ {
+ preview_type = Some(PreviewType::Markdown);
+ } else if SvgPreviewView::resolve_active_item_as_svg_editor(workspace, cx).is_some()
+ {
+ preview_type = Some(PreviewType::Svg);
+ }
+ });
+ }
+
+ let preview_type = preview_type?;
+
+ let (button_id, tooltip_text, open_action, open_to_side_action, open_action_for_tooltip) =
+ match preview_type {
+ PreviewType::Markdown => (
+ "toggle-markdown-preview",
+ "Preview Markdown",
+ Box::new(MarkdownOpenPreview) as Box<dyn gpui::Action>,
+ Box::new(MarkdownOpenPreviewToTheSide) as Box<dyn gpui::Action>,
+ &markdown_preview::OpenPreview as &dyn gpui::Action,
+ ),
+ PreviewType::Svg => (
+ "toggle-svg-preview",
+ "Preview SVG",
+ Box::new(SvgOpenPreview) as Box<dyn gpui::Action>,
+ Box::new(SvgOpenPreviewToTheSide) as Box<dyn gpui::Action>,
+ &svg_preview::OpenPreview as &dyn gpui::Action,
+ ),
+ };
+
+ let alt_click = gpui::Keystroke {
+ key: "click".into(),
+ modifiers: Modifiers::alt(),
+ ..Default::default()
+ };
+
+ let button = IconButton::new(button_id, IconName::Eye)
+ .shape(IconButtonShape::Square)
+ .icon_size(IconSize::Small)
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |window, cx| {
+ Tooltip::with_meta(
+ tooltip_text,
+ Some(open_action_for_tooltip),
+ format!("{} to open in a split", text_for_keystroke(&alt_click, cx)),
+ window,
+ cx,
+ )
+ })
+ .on_click(move |_, window, cx| {
+ if let Some(workspace) = workspace_handle.upgrade() {
+ workspace.update(cx, |_, cx| {
+ if window.modifiers().alt {
+ window.dispatch_action(open_to_side_action.boxed_clone(), cx);
+ } else {
+ window.dispatch_action(open_action.boxed_clone(), cx);
+ }
+ });
+ }
+ });
+
+ Some(button.into_any_element())
+ }
+}