IJP-778 Reader mode: A special mode for code browsing and exploring

GitOrigin-RevId: b9d4370c6ff8bf2485f863cde83c55c9f3c9a318
This commit is contained in:
Dmitry.Krasilschikov
2020-05-11 17:03:45 +00:00
committed by intellij-monorepo-bot
parent 376b4ffb49
commit 79c84d6834
16 changed files with 441 additions and 2 deletions
@@ -304,3 +304,16 @@ button.do.not.override=Do Not Override
label.filter.value=Filter value:
slice.filter.not.supported=Filtering is not supported
slice.analysis.title.filter=(filter: {0})
checkbox.breadcrumbs=Show breadcrumbs
configurable.reader.mode=Reader mode
checkbox.inlays=Show inlay hints
checkbox.rendered.docs=Show Rendered Docs
checkbox.ligatures=Show font ligatures
title.reader.mode=Reader Mode
checkbox.hide.warnings=Hide warnings
titled.border.mode=Enter to Reader Mode
radio.reader.mode.readonly=Show readonly files in Read Mode
radio.reader.unmodified.modules=Show files from unmodified modules in teh Rewader Mode
checkbox.reader.mode.enabled=Enable the Reader Mode
titled.border.reader.mode.settings=Settings
checkbox.line.spacing=Increase Line Spacing
@@ -0,0 +1,38 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.actions
import com.intellij.codeInsight.actions.ReaderModeFileEditorListener.Companion.matchMode
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ToggleAction
import com.intellij.openapi.application.Experiments
class ReaderModeActionGroup : DefaultActionGroup()
class ReaderModeAction : ToggleAction() {
override fun update(e: AnActionEvent) {
if (!Experiments.getInstance().isFeatureEnabled("editor.reader.mode")) {
e.presentation.isEnabledAndVisible = false
return
}
super.update(e)
val project = e.project ?: return
val file = PlatformDataKeys.VIRTUAL_FILE.getData(e.dataContext)?: return
e.presentation.isEnabledAndVisible = matchMode(project, file)
e.presentation.icon = if (ReaderModeSettings.instance(project).enabled) AllIcons.Actions.Edit else AllIcons.Actions.Show
}
override fun setSelected(e: AnActionEvent, readerMode: Boolean) {
val project = e.project ?: return
ReaderModeSettings.instance(project).enabled = readerMode
project.messageBus.syncPublisher(READER_MODE_TOPIC).modeChanged(project)
}
override fun isSelected(e: AnActionEvent): Boolean {
return ReaderModeSettings.instance(e.project ?: return false).enabled
}
}
@@ -0,0 +1,155 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.actions
import com.intellij.application.options.editor.CheckboxDescriptor
import com.intellij.application.options.editor.checkBox
import com.intellij.codeInsight.actions.ReaderMode.READ_ONLY_FILES
import com.intellij.codeInsight.actions.ReaderMode.UNMODIFIED_MODULE_FILES
import com.intellij.lang.LangBundle
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.options.BoundSearchableConfigurable
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogPanel
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.layout.*
class ReaderModeConfigurable(val project: Project) : BoundSearchableConfigurable(LangBundle.message("configurable.reader.mode"), "READER_MODE_HELP") {
private val settings get() = ReaderModeSettings.instance(project)
private val cdBreadcrumbs get() = CheckboxDescriptor(LangBundle.message("checkbox.breadcrumbs"), settings::showBreadcrumbs)
private val cdInlays get() = CheckboxDescriptor(LangBundle.message("checkbox.inlays"), settings::showInlaysHints)
private val cdRenderedDocs get() = CheckboxDescriptor(LangBundle.message("checkbox.rendered.docs"), settings::showRenderedDocs)
private val cdLigatures get() = CheckboxDescriptor(LangBundle.message("checkbox.ligatures"), settings::showLigatures)
private val cdLineSpacing get() = CheckboxDescriptor(LangBundle.message("checkbox.line.spacing"), settings::increaseLineSpacing)
private val cdWarnings get() = CheckboxDescriptor(LangBundle.message("checkbox.hide.warnings"), settings::hideWarnings)
private val cdEnabled get() = CheckboxDescriptor(LangBundle.message("checkbox.reader.mode.enabled"), settings::enabled)
override fun createPanel(): DialogPanel {
return panel {
lateinit var enabled: CellBuilder<JBCheckBox>
row {
enabled = checkBox(cdEnabled)
}
titledRow(LangBundle.message("titled.border.mode")) {
row {
radioButton(LangBundle.message("radio.reader.mode.readonly"),
{ return@radioButton settings.mode == READ_ONLY_FILES },
{ settings.mode = READ_ONLY_FILES })
.enableIf(enabled.selected)
}
row {
radioButton(LangBundle.message("radio.reader.unmodified.modules"),
{ return@radioButton settings.mode == UNMODIFIED_MODULE_FILES },
{ settings.mode = UNMODIFIED_MODULE_FILES })
.enableIf(enabled.selected)
}
}.enableIf(enabled.selected)
titledRow(LangBundle.message("titled.border.reader.mode.settings")) {
row {
checkBox(cdBreadcrumbs).enableIf(enabled.selected)
}
row {
checkBox(cdRenderedDocs).enableIf(enabled.selected)
}
row {
checkBox(cdLigatures).enableIf(enabled.selected)
}
row {
checkBox(cdLineSpacing).enableIf(enabled.selected)
}
row {
checkBox(cdInlays).enableIf(enabled.selected)
}
row {
checkBox(cdWarnings).enableIf(enabled.selected)
}
}.enableIf(enabled.selected)
}
}
override fun apply() {
super.apply()
project.messageBus.syncPublisher(READER_MODE_TOPIC).modeChanged(project)
}
}
enum class ReaderMode {
READ_ONLY_FILES, UNMODIFIED_MODULE_FILES
}
@State(name = "ReaderModeSettings", storages = [Storage("editor.xml")])
class ReaderModeSettings : PersistentStateComponent<ReaderModeSettings.State> {
companion object {
@JvmStatic
fun instance(project: Project): ReaderModeSettings {
return ServiceManager.getService(project, ReaderModeSettings::class.java)
}
}
private var myState = State()
data class State(
var showBreadcrumbs: Boolean = true,
var showLigatures: Boolean = true,
var increaseLineSpacing: Boolean = true,
var showRenderedDocs: Boolean = true,
var showInlayHints: Boolean = true,
var hideWarnings: Boolean = true,
var enabled: Boolean = true,
var mode: ReaderMode = READ_ONLY_FILES
)
var showBreadcrumbs: Boolean
get() = state.showBreadcrumbs
set(value) {
state.showBreadcrumbs = value
}
var showLigatures: Boolean
get() = state.showLigatures
set(value) {
state.showLigatures = value
}
var increaseLineSpacing: Boolean
get() = state.showLigatures
set(value) {
state.showLigatures = value
}
var showInlaysHints: Boolean
get() = state.showInlayHints
set(value) {
state.showInlayHints = value
}
var showRenderedDocs: Boolean
get() = state.showRenderedDocs
set(value) {
state.showRenderedDocs = value
}
var hideWarnings: Boolean
get() = state.hideWarnings
set(value) {
state.hideWarnings = value
}
var enabled: Boolean
get() = state.enabled
set(value) {
state.enabled = value
}
var mode: ReaderMode
get() = state.mode
set(value) {
state.mode = value
}
override fun getState(): State = run { return myState }
override fun loadState(state: State) = run { myState = state }
}
@@ -0,0 +1,15 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.actions
import com.intellij.openapi.application.Experiments
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurableProvider
import com.intellij.openapi.project.Project
class ReaderModeConfigurableProvider(val project: Project) : ConfigurableProvider() {
override fun createConfigurable(): Configurable? {
return ReaderModeConfigurable(project)
}
override fun canCreateConfigurable() = Experiments.getInstance().isFeatureEnabled("editor.reader.mode")
}
@@ -0,0 +1,40 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.actions
import com.intellij.codeInsight.actions.ReaderModeSettings.Companion.instance
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
class ReaderModeFileEditorListener : FileEditorManagerListener {
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
applyReaderMode(source.project, source.getSelectedEditor(file))
}
companion object {
private var EP_READER_MODE_PROVIDER = ExtensionPointName<ReaderModeProvider>("com.intellij.readerModeProvider")
fun applyReaderMode(project: Project, selectedEditor: FileEditor?) {
if (selectedEditor !is PsiAwareTextEditorImpl) return
val file = selectedEditor.file
if (!matchMode(project, file)) return
EP_READER_MODE_PROVIDER.extensions().forEach { it.applyModeChanged(project, selectedEditor.editor, instance(project).enabled) }
}
fun matchMode(project: Project, file: VirtualFile): Boolean {
return when (instance(project).mode) {
ReaderMode.READ_ONLY_FILES -> !file.isWritable
ReaderMode.UNMODIFIED_MODULE_FILES -> checkUnmodifiedModules(file)
}
}
private fun checkUnmodifiedModules(file: VirtualFile): Boolean {
return false
}
}
}
@@ -0,0 +1,21 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.actions
import com.intellij.codeInsight.actions.ReaderModeFileEditorListener.Companion.applyReaderMode
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.util.messages.Topic
import java.util.*
interface ReaderModeListener : EventListener {
fun modeChanged(project: Project)
}
@Topic.ProjectLevel
val READER_MODE_TOPIC = Topic(ReaderModeListener::class.java)
class ReaderModeSettingsListener : ReaderModeListener {
override fun modeChanged(project: Project) {
FileEditorManager.getInstance(project).allEditors.forEach { editor -> applyReaderMode(project, editor) }
}
}
@@ -0,0 +1,12 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.actions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
/**
* Use it to provide an activity that should be applied for editor in Read Mode
*/
interface ReaderModeProvider {
fun applyModeChanged(project: Project, editor: Editor, readerMode: Boolean) {}
}
@@ -0,0 +1,97 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.actions
import com.intellij.codeInsight.daemon.impl.analysis.FileHighlightingSetting
import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil
import com.intellij.codeInsight.documentation.render.DocRenderItem
import com.intellij.codeInsight.hints.InlayHintsPassFactory
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.impl.AppEditorFontOptions
import com.intellij.openapi.editor.colors.impl.FontPreferencesImpl
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.xml.breadcrumbs.BreadcrumbsForceShownSettings
import com.intellij.xml.breadcrumbs.BreadcrumbsInitializingActivity
class BreadcrumbsReaderModeProvider : ReaderModeProvider {
override fun applyModeChanged(project: Project, editor: Editor, readerMode: Boolean) {
val showBreadcrumbs = (readerMode && ReaderModeSettings.instance(project).showBreadcrumbs)
|| EditorSettingsExternalizable.getInstance().isBreadcrumbsShown
BreadcrumbsForceShownSettings.setForcedShown(showBreadcrumbs, editor)
ApplicationManager.getApplication().invokeLater { BreadcrumbsInitializingActivity.reinitBreadcrumbsInAllEditors(project) }
}
}
class HighlightingReaderModeProvider : ReaderModeProvider {
override fun applyModeChanged(project: Project, editor: Editor, readerMode: Boolean) {
val highlighting =
if (readerMode && ReaderModeSettings.instance(project).hideWarnings) FileHighlightingSetting.SKIP_INSPECTION
else FileHighlightingSetting.FORCE_HIGHLIGHTING
HighlightLevelUtil.forceRootHighlighting(PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return, highlighting)
}
}
class LigaturesReaderModeProvider : ReaderModeProvider {
override fun applyModeChanged(project: Project, editor: Editor, readerMode: Boolean) {
val scheme = editor.colorsScheme
val preferences = scheme.fontPreferences
var useLigatures: Boolean = (AppEditorFontOptions.getInstance().fontPreferences as FontPreferencesImpl).useLigatures()
if (readerMode) {
if (ReaderModeSettings.instance(project).showLigatures) {
useLigatures = true
val ligaturesFontPreferences = FontPreferencesImpl()
preferences.copyTo(ligaturesFontPreferences)
ligaturesFontPreferences.setUseLigatures(useLigatures)
scheme.fontPreferences = ligaturesFontPreferences
(scheme.fontPreferences as FontPreferencesImpl).setUseLigatures(true)
}
}
}
}
class FontReaderModeProvider : ReaderModeProvider {
override fun applyModeChanged(project: Project, editor: Editor, readerMode: Boolean) {
if (readerMode) {
if (ReaderModeSettings.instance(project).increaseLineSpacing) {
editor.colorsScheme.lineSpacing = 1.4f
}
}
else {
editor.colorsScheme.lineSpacing = AppEditorFontOptions.getInstance().fontPreferences.lineSpacing
}
}
}
class DocsRenderingReaderModeProvider : ReaderModeProvider {
override fun applyModeChanged(project: Project, editor: Editor, readerMode: Boolean) {
if (readerMode) {
if (ReaderModeSettings.instance(project).showRenderedDocs) {
EditorSettingsExternalizable.getInstance().isDocCommentRenderingEnabled = true
}
}
else {
EditorSettingsExternalizable.getInstance().isDocCommentRenderingEnabled = false
}
DocRenderItem.resetToDefaultEditorState(editor)
}
}
class InlaysReaderModeProvider : ReaderModeProvider {
override fun applyModeChanged(project: Project, editor: Editor, readerMode: Boolean) {
if (readerMode) {
if (ReaderModeSettings.instance(project).showInlaysHints) {
InlayHintsPassFactory.setHintsEnabled(editor, true)
}
}
else {
InlayHintsPassFactory.setHintsEnabled(editor, false)
}
}
}
@@ -221,6 +221,14 @@ public class DocRenderItem {
});
}
public static void resetToDefaultEditorState(@NotNull Editor editor) {
resetToDefaultState(editor);
DocRenderPassFactory.forceRefreshOnNextPass(editor);
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
DaemonCodeAnalyzer.getInstance(project).restart();
}
}
public static void resetAllToDefaultState() {
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
resetToDefaultState(editor);
@@ -0,0 +1,9 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.editor.impl
import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider
class ReaderModeFloatingToolbarProvider : AbstractFloatingToolbarProvider("ReaderModeActionGroup") {
override val priority = 200
override val autoHideable = true
}
@@ -25,7 +25,7 @@ import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
final class BreadcrumbsInitializingActivity implements StartupActivity.DumbAware {
public final class BreadcrumbsInitializingActivity implements StartupActivity.DumbAware {
@Override
public void runActivity(@NotNull Project project) {
if (project.isDefault() || ApplicationManager.getApplication().isUnitTestMode() || project.isDisposed()) {
@@ -73,7 +73,7 @@ final class BreadcrumbsInitializingActivity implements StartupActivity.DumbAware
}
}
private static void reinitBreadcrumbsInAllEditors(@NotNull Project project) {
public static void reinitBreadcrumbsInAllEditors(@NotNull Project project) {
if (project.isDisposed()) return;
FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
for (VirtualFile virtualFile : fileEditorManager.getOpenFiles()) {
@@ -592,6 +592,7 @@
<extensionPoint name="fileTypeStatisticProvider" interface="com.intellij.internal.statistic.fileTypes.FileTypeStatisticProvider" dynamic="true"/>
<extensionPoint name="lang.directNavigationProvider" dynamic="true" interface="com.intellij.navigation.DirectNavigationProvider"/>
<extensionPoint name="readerModeProvider" dynamic="true" interface="com.intellij.codeInsight.actions.ReaderModeProvider"/>
</extensionPoints>
</idea-plugin>
@@ -250,6 +250,26 @@
<applicationService serviceInterface="com.intellij.psi.search.IndexPatternOptimizer"
serviceImplementation="com.intellij.psi.search.IndexPatternOptimizerImpl"/>
<projectService serviceImplementation="com.intellij.codeInsight.actions.ReaderModeSettings"/>
<projectConfigurable groupId="editor"
id="editor.reader.mode" key="title.reader.mode"
bundle="messages.LangBundle"
order="after editor.preferences.import"
provider="com.intellij.codeInsight.actions.ReaderModeConfigurableProvider"/>
<experimentalFeature id="editor.reader.mode" percentOfUsers="100">
<description>The Reader Mode is intended for comfortable code browsing rather than modification. It turns off warnings, show more inlay hints, enhance font presentation.
</description>
</experimentalFeature>
<readerModeProvider implementation="com.intellij.codeInsight.actions.BreadcrumbsReaderModeProvider"/>
<readerModeProvider implementation="com.intellij.codeInsight.actions.InlaysReaderModeProvider"/>
<readerModeProvider implementation="com.intellij.codeInsight.actions.FontReaderModeProvider"/>
<readerModeProvider implementation="com.intellij.codeInsight.actions.LigaturesReaderModeProvider"/>
<readerModeProvider implementation="com.intellij.codeInsight.actions.HighlightingReaderModeProvider"/>
<readerModeProvider implementation="com.intellij.codeInsight.actions.DocsRenderingReaderModeProvider"/>
<projectService serviceImplementation="com.intellij.psi.codeStyle.ProjectCodeStyleSettingsManager" preload="true"/>
<projectService serviceImplementation="com.intellij.psi.codeStyle.LegacyCodeStyleSettingsManager"/>
@@ -1012,6 +1012,8 @@
<projectService serviceImplementation="com.intellij.openapi.fileTypes.impl.FileTypeHintPersistentComponent" configurationSchemaKey="fileTypes"/>
<fileTypeOverrider implementation="com.intellij.openapi.fileTypes.impl.FileTypeHintService"/>
<editorFloatingToolbarProvider implementation="com.intellij.openapi.editor.impl.ReaderModeFloatingToolbarProvider"/>
</extensions>
<applicationListeners>
<listener class="com.intellij.ide.plugins.DynamicPluginsFrameStateListener" topic="com.intellij.ide.FrameStateListener"/>
@@ -59,5 +59,9 @@
<!-- another one for project level -->
<listener class="com.intellij.notification.impl.MacEventReader" topic="com.intellij.notification.Notifications" os="mac" activeInHeadlessMode="false"/>
<listener class="com.intellij.codeInsight.actions.ReaderModeFileEditorListener"
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener" activeInHeadlessMode="false"/>
<listener class="com.intellij.codeInsight.actions.ReaderModeSettingsListener"
topic="com.intellij.codeInsight.actions.ReaderModeListener" activeInHeadlessMode="false"/>
</projectListeners>
</idea-plugin>
@@ -1225,5 +1225,9 @@
<action class="com.intellij.openapi.editor.actions.ShowFontsUsedByEditorAction" id="ShowFontsUsedByEditor"/>
<group id="AnalyzeStacktraceToolbar"/>
<group id="ReaderModeActionGroup" class="com.intellij.codeInsight.actions.ReaderModeActionGroup">
<action id="ReaderModeAction" class="com.intellij.codeInsight.actions.ReaderModeAction" icon="AllIcons.Actions.Edit"/>
</group>
</actions>
</idea-plugin>