[Markdown][IJPL-92156] Add the ability to change markdown preview font size

GitOrigin-RevId: 996ec1f34319107ae437d69866328c47ac6cb346
This commit is contained in:
Ekaterina Berezhko
2024-07-16 18:08:35 +03:00
committed by intellij-monorepo-bot
parent 71c20d85f7
commit 4a576602a8
13 changed files with 515 additions and 56 deletions

View File

@@ -55,7 +55,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{"control 1", "FileChooser.GotoHome", "GotoBookmark1", "DuplicatesForm.SendToLeft"},
{"control 2", "FileChooser.GotoProject", "GotoBookmark2", "DuplicatesForm.SendToRight"},
{"control 3", "GotoBookmark3", "FileChooser.GotoModule"},
{"control ADD", "ExpandAll", "ExpandExpandableComponent", "ExpandRegion"},
{"control ADD", "ExpandAll", "ExpandExpandableComponent", "ExpandRegion", "Markdown.Preview.IncreaseFontSize"},
{"control A", "$SelectAll", "Terminal.MoveCaretToLineStart"},
{"control B", "GotoDeclaration", "org.intellij.plugins.markdown.ui.actions.styling.ToggleBoldAction"},
{"control C", "$Copy", "Terminal.ClearPrompt", "Terminal.CopySelectedText", "org.jetbrains.r.console.RConsoleView.RInterruptAction",
@@ -69,19 +69,19 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{"control ENTER", "Console.Execute.Multiline", "DirDiffMenu.SynchronizeDiff.All", "EditorSplitLine", "NotebookRunCellAction",
"PyExecuteCellAction", "SplitChooser.Duplicate", "Terminal.SmartCommandExecution.Run", "ViewSource",
"org.jetbrains.r.actions.RunSelection", "Docker.RemoteServers.StartComposeService", "GraphQLExecuteEditor"},
{"control EQUALS", "ExpandAll", "ExpandExpandableComponent", "ExpandRegion"},
{"control EQUALS", "ExpandAll", "ExpandExpandableComponent", "ExpandRegion", "Markdown.Preview.IncreaseFontSize"},
{"control F5", "Refresh", "Rerun"},
{"control I", "ImplementMethods", "org.intellij.plugins.markdown.ui.actions.styling.ToggleItalicAction"},
{"control INSERT", "$Copy", "Terminal.CopySelectedText"},
{"control M", "EditorScrollToCenter", "Vcs.ShowMessageHistory"},
{"control MINUS", "CollapseAll", "CollapseExpandableComponent", "CollapseRegion"},
{"control MINUS", "CollapseAll", "CollapseExpandableComponent", "CollapseRegion", "Markdown.Preview.DecreaseFontSize"},
{"control N", "FileChooser.NewFolder", "GotoClass"},
{"control P", "FileChooser.TogglePathBar", "ParameterInfo"},
{"control PERIOD", "EditorChooseLookupItemDot", "CollapseSelection"},
{"control R", "Replace", "Terminal.SearchInCommandHistory", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{"control SLASH", "CommentByLineComment", "Graph.ActualSize"},
{"control SPACE", "CodeCompletion", "ChangesView.SetDefault"},
{"control SUBTRACT", "CollapseAll", "CollapseExpandableComponent", "CollapseRegion"},
{"control SUBTRACT", "CollapseAll", "CollapseExpandableComponent", "CollapseRegion", "Markdown.Preview.DecreaseFontSize"},
{"control U", "GotoSuperMethod", "CommanderSwapPanels", "org.intellij.plugins.markdown.ui.actions.styling.InsertImageAction"},
{"control UP", "EditorScrollUp", "EditorLookupUp", "MethodOverloadSwitchUp", "NotebookSelectCellAboveAction",
"Terminal.SelectLastBlock", "Terminal.SelectBlockAbove"},
@@ -290,13 +290,13 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{"ctrl H", "Replace", "Vcs.ShowMessageHistory"},
{"ctrl I", "IncrementalSearch", "org.intellij.plugins.markdown.ui.actions.styling.ToggleItalicAction"},
{"ctrl L", "EditorDeleteLine", "Terminal.ClearBuffer"},
{"ctrl MINUS", "Back", "CollapseAll", "CollapseExpandableComponent"},
{"ctrl MINUS", "Back", "CollapseAll", "CollapseExpandableComponent", "Markdown.Preview.DecreaseFontSize"},
{"ctrl N", "FileChooser.NewFolder", "NewElement"},
{"ctrl P", "FileChooser.TogglePathBar", "Print"},
{"ctrl PERIOD", "EditorChooseLookupItemDot", "ShowIntentionActions"},
{"ctrl R", SECOND_STROKE, "Terminal.SearchInCommandHistory", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{"ctrl R,R", "ChangesView.Rename", "Git.Rename.Local.Branch", "Git.Reword.Commit", "RenameElement", "ShelvedChanges.Rename"},
{"ctrl SUBTRACT", "Back", "CollapseAll", "CollapseExpandableComponent"},
{"ctrl SUBTRACT", "Back", "CollapseAll", "CollapseExpandableComponent", "Markdown.Preview.DecreaseFontSize"},
{"ctrl alt B", "ViewBreakpoints", "org.jetbrains.r.rendering.chunk.RunChunksAboveAction"},
{"ctrl alt ENTER", "ReformatCode", "org.jetbrains.r.actions.DebugSelection"},
{"ctrl alt F", "ActivateStructureToolWindow", "ShowFilterPopup"},
@@ -333,8 +333,8 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{"ctrl alt LEFT", "GotoSuperMethod", "ResizeToolWindowLeft"},
{"meta Y", "$Redo", "QuickImplementations"},
{"meta alt B", "ViewBreakpoints", "org.jetbrains.r.rendering.chunk.RunChunksAboveAction"},
{"meta SUBTRACT", "Back", "CollapseAll", "CollapseExpandableComponent"},
{"meta MINUS", "Back", "CollapseAll", "CollapseExpandableComponent"},
{"meta SUBTRACT", "Back", "CollapseAll", "CollapseExpandableComponent", "Markdown.Preview.DecreaseFontSize"},
{"meta MINUS", "Back", "CollapseAll", "CollapseExpandableComponent", "Markdown.Preview.DecreaseFontSize"},
{"shift F5", "Graph.ApplyCurrentLayout", "Stop"},
{"shift meta alt R", "ChooseRunConfiguration", "ForceRefresh"},
{"ctrl alt F", "ActivateStructureToolWindow", "EditorNextWord", "ShowFilterPopup"},
@@ -423,6 +423,8 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{"shift ctrl alt LEFT", "NextEditorTab", "Diff.NextChange"},
{"shift ctrl alt RIGHT", "PreviousEditorTab", "Diff.PrevChange"},
{"shift ctrl DIVIDE", "CollapseAll", "CollapseAllRegions"},
{"ctrl MINUS", "EditorDecreaseFontSizeGlobal", "Markdown.Preview.DecreaseFontSize"},
{"ctrl EQUALS", "EditorIncreaseFontSizeGlobal", "Markdown.Preview.IncreaseFontSize"},
}),
Map.entry("NetBeans 6.5", new String[][]{
{"F4", "RunToCursor", "EditSource"},
@@ -495,6 +497,8 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{"shift meta U", "FindUsagesInFile", "ShelveChanges.UnshelveWithDialog", "Markdown.Styling.CreateLink"},
{"shift meta X", "EditorToggleCase", "com.jetbrains.php.framework.FrameworkRunConsoleAction"},
{"shift meta DIVIDE", "CollapseAll", "CollapseAllRegions"},
{"meta MINUS", "EditorDecreaseFontSizeGlobal", "Markdown.Preview.DecreaseFontSize"},
{"meta EQUALS", "EditorIncreaseFontSizeGlobal", "Markdown.Preview.IncreaseFontSize"},
}),
Map.entry("Sublime Text", new String[][]{
{"F2", "ChangesView.Rename", "Console.TableResult.EditValue", "Git.Reword.Commit", "Git.Rename.Local.Branch",
@@ -502,22 +506,22 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
"XDebugger.SetValue", "Table-startEditing", "Tree-startEditing", "Console.TableResult.GotoReferencedResult"},
{"F12", "GotoDeclaration", "WebInspector.Browser.Selection.Toggle"},
{"alt MINUS", "Back", "RInsertAssignmentAction"},
{"ctrl ADD", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent"},
{"ctrl ADD", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent", "Markdown.Preview.IncreaseFontSize"},
{"ctrl B", "Compile", "org.intellij.plugins.markdown.ui.actions.styling.ToggleBoldAction"},
{"ctrl D", "CompareTwoFiles", "Diff.ShowDiff", "FileChooser.GotoDesktop", "SelectNextOccurrence", "SendEOF",
"Terminal.CloseSession", "org.jetbrains.r.console.RConsoleView.REofAction"},
{"ctrl ENTER", "Console.Execute.Multiline", "DirDiffMenu.SynchronizeDiff.All", "EditorStartNewLine", "NotebookRunCellAction",
"PyExecuteCellAction", "ViewSource", "org.jetbrains.r.actions.RunSelection", "Terminal.SmartCommandExecution.Run",
"SplitChooser.Duplicate", "Docker.RemoteServers.StartComposeService", "GraphQLExecuteEditor"},
{"ctrl EQUALS", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent"},
{"ctrl EQUALS", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent", "Markdown.Preview.IncreaseFontSize"},
{"ctrl I", "IncrementalSearch", "org.intellij.plugins.markdown.ui.actions.styling.ToggleItalicAction"},
{"ctrl L", "EditorSelectLine", "Terminal.ClearBuffer"},
{"ctrl M", "EditorMatchBrace", "Vcs.ShowMessageHistory"},
{"ctrl MINUS", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize"},
{"ctrl MINUS", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize", "Markdown.Preview.DecreaseFontSize"},
{"ctrl N", "FileChooser.NewFolder", "NewElement"},
{"ctrl P", "FileChooser.TogglePathBar", "GotoFile"},
{"ctrl R", "FileStructurePopup", "Terminal.SearchInCommandHistory", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{"ctrl SUBTRACT", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize"},
{"ctrl SUBTRACT", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize", "Markdown.Preview.DecreaseFontSize"},
{"ctrl V", "EditorPasteSimple", "Terminal.Paste", "JupyterNotebookPasteCellCommandModeAction"},
{"ctrl W", "CloseContent", "Terminal.DeletePreviousWord"},
{"ctrl alt DOWN", "Console.TableResult.NextPage", "EditorCloneCaretBelow"},
@@ -539,7 +543,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
"XDebugger.SetValue", "Table-startEditing", "Tree-startEditing", "Console.TableResult.GotoReferencedResult"},
{"F12", "GotoDeclaration", "WebInspector.Browser.Selection.Toggle"},
{"ctrl R", "GotoNextBookmark", "Terminal.SearchInCommandHistory"},
{"meta ADD", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent"},
{"meta ADD", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent", "Markdown.Preview.IncreaseFontSize"},
{"meta B", "Compile", "org.intellij.plugins.markdown.ui.actions.styling.ToggleBoldAction"},
{"meta BACK_SPACE", "EditorDeleteToLineStart", "$Delete"},
{"meta D", "CompareTwoFiles", "Diff.ShowDiff", "FileChooser.GotoDesktop", "SelectNextOccurrence", "SendEOF"},
@@ -547,14 +551,14 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{"meta ENTER", "Console.Execute.Multiline", "DirDiffMenu.SynchronizeDiff.All", "EditorStartNewLine", "ViewSource",
"org.jetbrains.r.actions.RunSelection", "Terminal.SmartCommandExecution.Run", "SplitChooser.Duplicate",
"Docker.RemoteServers.StartComposeService", "GraphQLExecuteEditor"},
{"meta EQUALS", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent"},
{"meta EQUALS", "EditorIncreaseFontSize", "ExpandAll", "ExpandExpandableComponent", "Markdown.Preview.IncreaseFontSize"},
{"meta I", "DatabaseView.PropertiesAction", "IncrementalSearch", "org.intellij.plugins.markdown.ui.actions.styling.ToggleItalicAction"},
{"meta K", SECOND_STROKE, "Terminal.ClearBuffer"},
{"meta L", "EditorSelectLine", "Terminal.ClearBuffer"},
{"meta MINUS", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize"},
{"meta MINUS", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize", "Markdown.Preview.DecreaseFontSize"},
{"meta P", "FileChooser.TogglePathBar", "GotoFile"},
{"meta R", "FileStructurePopup", "Refresh", "Rerun", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{"meta SUBTRACT", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize"},
{"meta SUBTRACT", "CollapseAll", "CollapseExpandableComponent", "EditorDecreaseFontSize", "Markdown.Preview.DecreaseFontSize"},
{"meta T", "GotoFile", "Terminal.NewTab"},
{"meta UP", "EditorTextStart", "FileChooser.GoToParent", "Terminal.SelectLastBlock", "Terminal.SelectBlockAbove"},
{"meta V", "EditorPasteSimple", "Terminal.Paste", "JupyterNotebookPasteCellCommandModeAction"},

View File

@@ -595,13 +595,25 @@
class="org.intellij.plugins.markdown.extensions.CleanupExtensionsExternalFilesAction"
icon="AllIcons.Actions.GC"/>
<group id="Markdown.Preview.FontSize" popup="true" compact="true">
<group id="Markdown.Preview.FontSize" popup="true">
<action id="Markdown.Preview.IncreaseFontSize"
class="org.intellij.plugins.markdown.ui.actions.ChangeFontSizeAction$Increase"/>
class="org.intellij.plugins.markdown.ui.actions.ChangeFontSizeAction$Increase">
<keyboard-shortcut first-keystroke="control ADD" keymap="$default"/>
<keyboard-shortcut first-keystroke="control EQUALS" keymap="$default"/>
</action>
<action id="Markdown.Preview.DecreaseFontSize"
class="org.intellij.plugins.markdown.ui.actions.ChangeFontSizeAction$Decrease"/>
<action id="Markdown.Preview.ResetFontSize"
class="org.intellij.plugins.markdown.ui.actions.ChangeFontSizeAction$Reset"/>
class="org.intellij.plugins.markdown.ui.actions.ChangeFontSizeAction$Decrease">
<keyboard-shortcut first-keystroke="control MINUS" keymap="$default"/>
<keyboard-shortcut first-keystroke="control SUBTRACT" keymap="$default"/>
</action>
</group>
<group id="Markdown.PreviewGroup">
<action id="Markdown.Preview.AdjustFontSize"
class="org.intellij.plugins.markdown.ui.actions.AdjustFontSizeAction"/>
</group>
<action id="Markdown.Preview.ResetFontSize"
class="org.intellij.plugins.markdown.ui.actions.ResetFontSizeAction"/>
</actions>
</idea-plugin>

View File

@@ -367,3 +367,6 @@ group.Markdown.Preview.FontSize.text=Preview Font Size
action.Markdown.Preview.IncreaseFontSize.text=Increase Preview Font Size
action.Markdown.Preview.DecreaseFontSize.text=Decrease Preview Font Size
action.Markdown.Preview.ResetFontSize.text=Reset Preview Font Size
action.Markdown.Preview.AdjustFontSize.text=Adjust Font Size\u2026
action.Markdown.Preview.FontSize.label.text=Font size:

View File

@@ -19,6 +19,7 @@ import com.intellij.openapi.fileTypes.UnknownFileType
import com.intellij.openapi.options.BoundSearchableConfigurable
import com.intellij.openapi.options.ex.Settings
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.ValidationInfo
@@ -27,7 +28,6 @@ import com.intellij.ui.EnumComboBoxModel
import com.intellij.ui.SimpleListCellRenderer
import com.intellij.ui.components.ActionLink
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBTextField
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.layout.ValidationInfoBuilder
import com.intellij.util.application
@@ -159,15 +159,19 @@ internal class MarkdownSettingsConfigurable(private val project: Project): Bound
}
}
private fun Row.previewFontSizeField(): Cell<JBTextField> {
return intTextField(range = 0..300).bindIntText(
private fun Row.previewFontSizeField(): Cell<ComboBox<Int>> {
return comboBox(fontSizeOptions).bindItem(
getter = { service<MarkdownPreviewSettings>().state.fontSize },
setter = { value ->
service<MarkdownPreviewSettings>().update { settings ->
settings.state.fontSize = value
if (value != null) {
settings.state.fontSize = value
}
}
}
)
).applyToComponent {
isEditable = true
}
}
private fun validateCustomStylesheetPath(builder: ValidationInfoBuilder, textField: TextFieldWithBrowseButton): ValidationInfo? {
@@ -374,5 +378,7 @@ internal class MarkdownSettingsConfigurable(private val project: Project): Bound
else -> ""
}
}
val fontSizeOptions = listOf(8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72)
}
}

View File

@@ -0,0 +1,94 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.ui.actions
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.IconLoader
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
import org.intellij.plugins.markdown.MarkdownBundle
import org.intellij.plugins.markdown.settings.MarkdownPreviewSettings
import org.intellij.plugins.markdown.settings.MarkdownSettingsConfigurable.Companion.fontSizeOptions
import org.intellij.plugins.markdown.ui.preview.MarkdownPreviewFileEditor
import org.intellij.plugins.markdown.ui.preview.MarkdownPreviewFileEditor.Companion.PREVIEW_POPUP_POINT
import org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel
import java.awt.FlowLayout
import javax.swing.BorderFactory
import javax.swing.JButton
import javax.swing.JPanel
import javax.swing.SwingConstants
class AdjustFontSizeAction: DumbAwareAction() {
private val previewSettings
get() = service<MarkdownPreviewSettings>()
override fun actionPerformed(event: AnActionEvent) {
val editor = MarkdownActionUtil.findMarkdownPreviewEditor(event)
checkNotNull(editor) { "Preview editor should be obtainable from the action event" }
val preview = editor.getUserData(MarkdownPreviewFileEditor.PREVIEW_BROWSER)?.get() ?: return
if (preview !is MarkdownJCEFHtmlPanel) {
return
}
val fontSizeLabel = JBLabel(previewSettings.state.fontSize.toString(), SwingConstants.CENTER).apply {
preferredSize = JBUI.size(22, 22)
}
val decreaseButton = JButton(AllIcons.General.Remove).apply {
border = BorderFactory.createEmptyBorder()
isContentAreaFilled = false
preferredSize = JBUI.size(22, 22)
setDisabledIcon(IconLoader.getDisabledIcon(AllIcons.General.Remove))
isEnabled = previewSettings.state.fontSize != fontSizeOptions.first()
}
val increaseButton = JButton(AllIcons.General.Add).apply {
border = BorderFactory.createEmptyBorder()
isContentAreaFilled = false
preferredSize = JBUI.size(22, 22)
setDisabledIcon(IconLoader.getDisabledIcon(AllIcons.General.Add))
isEnabled = previewSettings.state.fontSize != fontSizeOptions.last()
}
fun updateFontSize(transform: (Int) -> Int?) {
val currentSize = preview.getCurrentFontSize()
val newSize = transform(currentSize) ?: currentSize
previewSettings.state.fontSize = newSize
preview.changeFontSize(newSize)
fontSizeLabel.text = newSize.toString()
decreaseButton.isEnabled = newSize != fontSizeOptions.first()
increaseButton.isEnabled = newSize != fontSizeOptions.last()
}
val hintComponent = JPanel(FlowLayout(FlowLayout.LEFT, 10, 5))
hintComponent.add(JBLabel(MarkdownBundle.message("action.Markdown.Preview.FontSize.label.text")))
hintComponent.add(decreaseButton.apply { addActionListener { updateFontSize { fontSizeOptions.findLast { step -> step < it } } } })
hintComponent.add(fontSizeLabel.apply { text = preview.getCurrentFontSize().toString() })
hintComponent.add(increaseButton.apply { addActionListener { updateFontSize { fontSizeOptions.find { step -> step > it } } } })
val popup = JBPopupFactory.getInstance().createComponentPopupBuilder(hintComponent, hintComponent).setRequestFocus(true).createPopup()
val point = event.dataContext.getData(PREVIEW_POPUP_POINT)
if (point != null) {
popup.show(point)
} else {
popup.showInFocusCenter()
}
}
private fun MarkdownJCEFHtmlPanel.getCurrentFontSize() = getTemporaryFontSize() ?: previewSettings.state.fontSize
override fun update(event: AnActionEvent) {
val editor = MarkdownActionUtil.findMarkdownPreviewEditor(event)
event.presentation.isEnabledAndVisible = editor != null
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
}

View File

@@ -2,23 +2,20 @@ package org.intellij.plugins.markdown.ui.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.Key
import com.intellij.ui.jcef.JBCefApp
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.ui.awt.RelativePoint
import org.intellij.plugins.markdown.ui.preview.MarkdownPreviewFileEditor
import org.intellij.plugins.markdown.ui.preview.PreviewLAFThemeStyles
import org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel
import org.intellij.plugins.markdown.ui.preview.jcef.impl.executeJavaScript
private val FontSize = Key.create<Int>("Markdown.Preview.FontSize")
import org.intellij.plugins.markdown.ui.preview.jcef.zoomIndicator.PreviewZoomIndicatorManager
internal sealed class ChangeFontSizeAction(private val transform: (Int) -> Int): DumbAwareAction() {
class Increase: ChangeFontSizeAction(transform = { it + 1 })
class Decrease: ChangeFontSizeAction(transform = { (it - 1).coerceAtLeast(1) })
class Reset: ChangeFontSizeAction(transform = { PreviewLAFThemeStyles.defaultFontSize })
override fun actionPerformed(event: AnActionEvent) {
val editor = MarkdownActionUtil.findMarkdownPreviewEditor(event)
checkNotNull(editor) { "Preview editor should be obtainable from the action event" }
@@ -26,10 +23,12 @@ internal sealed class ChangeFontSizeAction(private val transform: (Int) -> Int):
if (preview !is MarkdownJCEFHtmlPanel) {
return
}
val currentSize = preview.getUserData(FontSize) ?: PreviewLAFThemeStyles.defaultFontSize
val currentSize = preview.getTemporaryFontSize() ?: PreviewLAFThemeStyles.defaultFontSize
val newSize = transform(currentSize)
preview.putUserData(FontSize, newSize)
preview.changeFontSize(newSize)
preview.changeFontSize(newSize, temporary = true)
val project = event.project
val balloon = project?.service<PreviewZoomIndicatorManager>()?.createOrGetBalloon(preview)
balloon?.show(RelativePoint.getSouthOf(preview.component), Balloon.Position.below)
}
override fun update(event: AnActionEvent) {
@@ -41,18 +40,3 @@ internal sealed class ChangeFontSizeAction(private val transform: (Int) -> Int):
return ActionUpdateThread.EDT
}
}
/**
* @param size Unscaled font size.
*/
internal fun MarkdownJCEFHtmlPanel.changeFontSize(size: Int) {
val scaled = JBCefApp.normalizeScaledSize(size)
// language=JavaScript
val code = """
|(function() {
| const styles = document.querySelector(":root").style;
| styles.setProperty("${PreviewLAFThemeStyles.Variables.FontSize}", "${scaled}px");
|})();
""".trimMargin()
executeJavaScript(code)
}

View File

@@ -0,0 +1,32 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.ui.actions
import com.intellij.ide.IdeBundle
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import org.intellij.plugins.markdown.ui.preview.MarkdownPreviewFileEditor.Companion.PREVIEW_JCEF_PANEL
import org.intellij.plugins.markdown.ui.preview.PreviewLAFThemeStyles
internal class ResetFontSizeAction: DumbAwareAction() {
override fun actionPerformed(event: AnActionEvent) {
val preview = event.getRequiredData(PREVIEW_JCEF_PANEL).get() ?: return
preview.changeFontSize(PreviewLAFThemeStyles.defaultFontSize, temporary = true)
}
override fun update(event: AnActionEvent) {
if (event.place == ActionPlaces.POPUP) {
event.presentation.text = IdeBundle.message("action.reset.font.size", PreviewLAFThemeStyles.defaultFontSize)
val toReset = PreviewLAFThemeStyles.defaultFontSize
val preview = event.dataContext.getData(PREVIEW_JCEF_PANEL)?.get() ?: return
val currentSize = preview.getTemporaryFontSize()
event.presentation.setEnabled(currentSize != toReset)
}
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
}

View File

@@ -2,6 +2,10 @@
package org.intellij.plugins.markdown.ui.preview
import com.intellij.CommonBundle
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.DataKey
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.readAction
import com.intellij.openapi.editor.Document
@@ -14,13 +18,16 @@ import com.intellij.openapi.fileEditor.FileEditorState
import com.intellij.openapi.fileEditor.TextEditorWithPreview
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.awt.RelativePoint
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.StartupUiUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,16 +37,21 @@ import org.intellij.plugins.markdown.MarkdownBundle
import org.intellij.plugins.markdown.settings.MarkdownExtensionsSettings
import org.intellij.plugins.markdown.settings.MarkdownSettings
import org.intellij.plugins.markdown.ui.preview.html.MarkdownUtil.generateMarkdownHtml
import org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel
import org.intellij.plugins.markdown.util.MarkdownPluginScope
import org.jetbrains.annotations.ApiStatus.Internal
import java.awt.AWTEvent
import java.awt.BorderLayout
import java.awt.Point
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.MouseEvent
import java.beans.PropertyChangeListener
import java.lang.ref.WeakReference
import javax.swing.JComponent
import javax.swing.JPanel
@Internal
class MarkdownPreviewFileEditor(
private val project: Project,
@@ -78,6 +90,33 @@ class MarkdownPreviewFileEditor(
}
}
})
StartupUiUtil.addAwtListener(AWTEvent.MOUSE_EVENT_MASK, this) { event ->
if (event is MouseEvent && event.id == MouseEvent.MOUSE_CLICKED && event.button == MouseEvent.BUTTON3
&& event.component.isShowing() && htmlPanelWrapper.isShowing()
&& component.containsScreenLocation(event.locationOnScreen)
) {
val context = SimpleDataContext.builder()
.setParent(DataManager.getInstance().getDataContext(event.component))
.add(PREVIEW_POPUP_POINT, RelativePoint.fromScreen(event.locationOnScreen))
.build()
val group = requireNotNull(ActionUtil.getActionGroup("Markdown.PreviewGroup"))
val popup = JBPopupFactory.getInstance().createActionGroupPopup(
null,
group,
context,
JBPopupFactory.ActionSelectionAid.MNEMONICS,
true
)
popup.showInScreenCoordinates(event.component, event.locationOnScreen)
}
}
}
private fun JComponent.containsScreenLocation(screenLocation: Point): Boolean {
val relativeX = screenLocation.x - locationOnScreen.x
val relativeY = screenLocation.y - locationOnScreen.y
return (relativeX >= 0 && relativeX < size.width && relativeY >= 0 && relativeY < size.height)
}
private suspend fun setupScrollHelper() {
@@ -238,5 +277,8 @@ class MarkdownPreviewFileEditor(
companion object {
val PREVIEW_BROWSER: Key<WeakReference<MarkdownHtmlPanel>> = Key.create("PREVIEW_BROWSER")
internal val PREVIEW_POPUP_POINT: DataKey<RelativePoint> = DataKey.create("PREVIEW_POPUP_POINT")
internal val PREVIEW_JCEF_PANEL: DataKey<WeakReference<MarkdownJCEFHtmlPanel>> = DataKey.create("PREVIEW_JCEF_PANEL")
}
}

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.intellij.plugins.markdown.ui.preview
import com.intellij.ide.ui.UISettings
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.colors.EditorColorsScheme
@@ -19,6 +20,7 @@ internal object PreviewLAFThemeStyles {
@Suppress("ConstPropertyName", "CssInvalidHtmlTagReference")
object Variables {
const val FontSize = "--default-font-size"
const val Scale = "--scale"
}
val defaultFontSize: Int
@@ -43,15 +45,19 @@ internal object PreviewLAFThemeStyles {
val markdownFenceBackground = JBColor(Color(212, 222, 231, 255 / 4), Color(212, 222, 231, 25))
val fontSize = JBCefApp.normalizeScaledSize(defaultFontSize)
val backgroundColor = scheme.defaultBackground.webRgba()
val scale = service<UISettings>().currentIdeScale
// language=CSS
return """
:root {
${Variables.FontSize}: ${fontSize}px;
${Variables.Scale}: ${scale};
}
body {
background-color: ${backgroundColor};
font-size: var(${Variables.FontSize}) !important;
transform: scale(var(${Variables.Scale})) !important;
transform-origin: 0 0;
}
body, p, blockquote, ul, ol, dl, table, pre, code, tr {

View File

@@ -1,16 +1,23 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.intellij.plugins.markdown.ui.preview.jcef
import com.intellij.ide.ui.UISettingsListener
import com.intellij.openapi.application.readAction
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.BaseProjectDirectories
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.components.JBLoadingPanel
import com.intellij.ui.components.JBViewport
import com.intellij.ui.components.Magnificator
import com.intellij.ui.jcef.JBCefApp
import com.intellij.ui.jcef.JBCefClient
import com.intellij.ui.jcef.JCEFHtmlPanel
@@ -32,16 +39,19 @@ import org.intellij.markdown.html.HtmlGenerator
import org.intellij.plugins.markdown.extensions.MarkdownBrowserPreviewExtension
import org.intellij.plugins.markdown.extensions.MarkdownConfigurableExtension
import org.intellij.plugins.markdown.settings.MarkdownPreviewSettings
import org.intellij.plugins.markdown.ui.actions.changeFontSize
import org.intellij.plugins.markdown.settings.MarkdownSettingsConfigurable.Companion.fontSizeOptions
import org.intellij.plugins.markdown.ui.preview.*
import org.intellij.plugins.markdown.ui.preview.jcef.impl.*
import org.intellij.plugins.markdown.ui.preview.jcef.zoomIndicator.PreviewZoomIndicatorManager
import org.intellij.plugins.markdown.util.MarkdownApplicationScope
import org.intellij.plugins.markdown.util.MarkdownPluginScope
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
import java.awt.BorderLayout
import java.awt.Point
import java.net.URL
import javax.swing.JComponent
import kotlin.math.round
import kotlin.time.Duration.Companion.milliseconds
class MarkdownJCEFHtmlPanel(
@@ -120,7 +130,7 @@ class MarkdownJCEFHtmlPanel(
data class Update(
val content: String,
val initialScrollOffset: Int,
val document: VirtualFile?
val document: VirtualFile?,
) : PreviewRequest
data class ReloadWithOffset(val offset: Int) : PreviewRequest
@@ -159,6 +169,17 @@ class MarkdownJCEFHtmlPanel(
connection.subscribe(MarkdownPreviewSettings.ChangeListener.TOPIC, MarkdownPreviewSettings.ChangeListener { settings ->
changeFontSize(settings.state.fontSize)
})
connection.subscribe(UISettingsListener.TOPIC, UISettingsListener { settings ->
val scale = settings.currentIdeScale
// language=JavaScript
val code = """
|(function() {
| const styles = document.querySelector(":root").style;
| styles.setProperty("${PreviewLAFThemeStyles.Variables.Scale}", "${scale}");
|})();
""".trimMargin()
executeJavaScript(code)
})
coroutineScope.launch {
val projectRoot = projectRoot.await()
@@ -272,20 +293,68 @@ class MarkdownJCEFHtmlPanel(
executeJavaScript("window.scrollController?.scrollBy($horizontal, $vertical)")
}
private var previewInnerComponent: JComponent? = null
private val TEMPORARY_FONT_SIZE = Key.create<Int>("Markdown.Preview.FontSize")
private fun createComponent(): JComponent {
val component = super.getComponent()
if (project == null || virtualFile == null) return component
previewInnerComponent = super.getComponent()
if (project == null || virtualFile == null) return previewInnerComponent!!
val panel = JBLoadingPanel(BorderLayout(), this)
previewInnerComponent!!.putClientProperty(Magnificator.CLIENT_PROPERTY_KEY, object: Magnificator {
override fun magnify(scale: Double, at: Point): Point {
val currentSize = this@MarkdownJCEFHtmlPanel.getTemporaryFontSize() ?: PreviewLAFThemeStyles.defaultFontSize
var fontSize = round(currentSize * scale).toInt()
fontSize = maxOf(fontSize, fontSizeOptions.first())
fontSize = minOf(fontSize, fontSizeOptions.last())
changeFontSize(fontSize, temporary = true)
return at
}
})
previewInnerComponent!!.addPropertyChangeListener(TEMPORARY_FONT_SIZE.toString()) {
val balloon = project.service<PreviewZoomIndicatorManager>().createOrGetBalloon(this@MarkdownJCEFHtmlPanel)
balloon?.show(RelativePoint.getSouthOf(previewInnerComponent!!), Balloon.Position.below)
}
coroutineScope.async(context = Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
panel.startLoading()
panel.add(component)
val viewPort = JBViewport()
viewPort.add(previewInnerComponent)
panel.add(viewPort)
projectRoot.await()
panel.stopLoading()
}
return panel
}
fun getTemporaryFontSize() = getUserData(TEMPORARY_FONT_SIZE)
/**
* @param size Unscaled font size.
*/
internal fun changeFontSize(size: Int, temporary: Boolean = false) {
if (temporary) {
val previousSize = getUserData(TEMPORARY_FONT_SIZE) ?: PreviewLAFThemeStyles.defaultFontSize
putUserData(TEMPORARY_FONT_SIZE, size)
previewInnerComponent?.firePropertyChange(TEMPORARY_FONT_SIZE.toString(), previousSize, size)
} else {
putUserData(TEMPORARY_FONT_SIZE, null)
}
val scaled = JBCefApp.normalizeScaledSize(size)
// language=JavaScript
val code = """
|(function() {
| const styles = document.querySelector(":root").style;
| styles.setProperty("${PreviewLAFThemeStyles.Variables.FontSize}", "${scaled}px");
|})();
""".trimMargin()
executeJavaScript(code)
}
private fun createFileSchemeResourcesProcessor(projectRoot: VirtualFile?): ResourceProvider? {
if (projectRoot == null) return null

View File

@@ -0,0 +1,92 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.ui.preview.jcef.zoomIndicator
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult
import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.ui.BalloonImpl
import com.intellij.ui.ExperimentalUI
import com.intellij.ui.JBColor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel
import org.intellij.plugins.markdown.util.MarkdownPluginScope
import java.lang.ref.WeakReference
import kotlin.time.Duration.Companion.seconds
@Service(Service.Level.PROJECT)
class PreviewZoomIndicatorManager(project: Project) {
private val coroutineScope = MarkdownPluginScope.createChildScope(project)
private val cancelBalloonRequests = MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private var balloon: Balloon? = null
private var preview: WeakReference<MarkdownJCEFHtmlPanel> = WeakReference(null)
init {
project.messageBus.connect().subscribe(AnActionListener.TOPIC, object : AnActionListener {
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
if (event.place == ActionPlaces.POPUP && action is PreviewZoomIndicatorView.SettingsAction) {
cancelCurrentPopup()
}
}
})
coroutineScope.launch(context = Dispatchers.EDT) {
cancelBalloonRequests.collectLatest {
val b = balloon ?: return@collectLatest
b.getView().isHovered().collectLatest { isHovered ->
if (!isHovered) {
delay(5.seconds)
cancelCurrentPopup()
} else {
check(cancelBalloonRequests.tryEmit(Unit))
}
}
}
}
}
fun createOrGetBalloon(preview: MarkdownJCEFHtmlPanel): Balloon? {
val view = PreviewZoomIndicatorView(preview)
val b = balloon
if (this.preview.refersTo(preview) && (b as? BalloonImpl)?.isVisible == true) {
b.getView().updateFontSize()
return null
}
cancelCurrentPopup()
val newUI = ExperimentalUI.isNewUI()
val b2 = JBPopupFactory.getInstance().createBalloonBuilder(view)
.setRequestFocus(false)
.setShadow(true)
.setFillColor(if (newUI) JBColor.namedColor("Toolbar.Floating.background", JBColor(0xEDEDED, 0x454A4D)) else view.background)
.setBorderColor(if (newUI) JBColor.namedColor("Toolbar.Floating.borderColor", JBColor(0xEBECF0, 0x43454A)) else JBColor.border())
.setShowCallout(false)
.setFadeoutTime(0)
.setHideOnKeyOutside(false)
.createBalloon().apply { setAnimationEnabled(false) }
balloon = b2
check(cancelBalloonRequests.tryEmit(Unit))
this.preview = WeakReference(preview)
return b2
}
fun cancelCurrentPopup() {
balloon?.hide()
balloon = null
preview.clear()
}
private fun Balloon.getView() = ((this as BalloonImpl).content as PreviewZoomIndicatorView)
}

View File

@@ -0,0 +1,109 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.ui.preview.jcef.zoomIndicator
import com.intellij.icons.AllIcons
import com.intellij.ide.IdeBundle
import com.intellij.ide.actions.ShowSettingsUtilImpl
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.ui.components.AnActionLink
import com.intellij.util.ui.JBUI
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import net.miginfocom.swing.MigLayout
import org.intellij.plugins.markdown.MarkdownBundle
import org.intellij.plugins.markdown.ui.preview.MarkdownPreviewFileEditor.Companion.PREVIEW_JCEF_PANEL
import org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.lang.ref.WeakReference
import javax.swing.JLabel
import javax.swing.JPanel
class PreviewZoomIndicatorView(private val preview: MarkdownJCEFHtmlPanel) : JPanel(MigLayout("novisualpadding, ins 0")) {
private val isHoveredFlow = MutableSharedFlow<Boolean>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val fontSizeLabel = JLabel(IdeBundle.message("action.reset.font.size.info", "000"))
private val settingsButton = SettingsButton(SettingsAction())
internal class SettingsAction: DumbAwareAction(IdeBundle.message("action.open.editor.settings.text"), "", AllIcons.General.Settings) {
override fun actionPerformed(e: AnActionEvent) {
val prj = e.project ?: return
val searchName = MarkdownBundle.message("markdown.settings.preview.font.size")
ShowSettingsUtilImpl.showSettingsDialog(prj, "Settings.Markdown", searchName)
}
}
internal class SettingsButton(settingsAction: SettingsAction) : ActionButton(settingsAction, settingsAction.templatePresentation.clone(), ActionPlaces.POPUP, JBUI.size(22, 22)) {
override fun performAction(e: MouseEvent?) {
val event = AnActionEvent.createFromInputEvent(e, myPlace, myPresentation, dataContext, false, true)
ActionUtil.performDumbAwareWithCallbacks(myAction, event) { actionPerformed(event) }
}
override fun isShowing() = true
}
private inner class PatchedActionLink(action: AnAction, event: AnActionEvent) : AnActionLink(action, ActionPlaces.POPUP) {
init {
text = event.presentation.text
autoHideOnDisable = false
isEnabled = event.presentation.isEnabled
event.presentation.addPropertyChangeListener {
if (it.propertyName == Presentation.PROP_ENABLED) {
isEnabled = it.newValue as Boolean
}
}
}
override fun uiDataSnapshot(sink: DataSink) {
super.uiDataSnapshot(sink)
sink[PREVIEW_JCEF_PANEL] = WeakReference(preview)
}
override fun isShowing() = true
}
private val resetLink = ActionManager.getInstance().getAction("Markdown.Preview.ResetFontSize").run {
val dataContext = SimpleDataContext.builder().add(PREVIEW_JCEF_PANEL, WeakReference(preview)).build()
val event = AnActionEvent.createFromInputEvent(null, ActionPlaces.POPUP,
null, dataContext,
false, true)
update(event)
fontSizeLabel.addPropertyChangeListener {
if (it.propertyName == "text") {
update(event)
}
}
PatchedActionLink(this, event)
}
init {
check(isHoveredFlow.tryEmit(false))
val mouseListener = object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent?) { check(isHoveredFlow.tryEmit(true)) }
override fun mouseExited(e: MouseEvent?) { check(isHoveredFlow.tryEmit(false)) }
}
addMouseListener(mouseListener)
resetLink.addMouseListener(mouseListener)
settingsButton.addMouseListener(mouseListener)
updateFontSize()
add(fontSizeLabel, "wmin 100, gapbottom 1, gapleft 3")
add(resetLink, "gapbottom 1")
add(settingsButton)
}
fun updateFontSize() {
fontSizeLabel.text = IdeBundle.message("action.reset.font.size.info", preview.getTemporaryFontSize())
}
fun isHovered(): Flow<Boolean> {
return isHoveredFlow
}
}

View File

@@ -35614,6 +35614,12 @@
<option name="markdown" hit="Markdown" />
<option name="uri" hit="URI:" />
</configurable>
<configurable id="Settings.Markdown" configurable_name="Markdown">
<option name="" hit="" />
<option name="preview" hit="Preview font size:" />
<option name="font" hit="Preview font size:" />
<option name="size" hit="Preview font size:" />
</configurable>
<configurable id="settings.nodejs" configurable_name="Node.js and NPM">
<option name="" hit="" />
<option name="assistance" hit="Coding Assistance" />