/* * Copyright 2003-2023 The IdeaVim authors * * Use of this source code is governed by an MIT-style * license that can be found in the LICENSE.txt file or at * https://opensource.org/licenses/MIT. */ package com.maddyhome.idea.vim.ui import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.wm.StatusBar import com.intellij.openapi.wm.StatusBarWidget import com.intellij.openapi.wm.StatusBarWidgetFactory import com.intellij.openapi.wm.WindowManager import com.intellij.openapi.wm.impl.status.EditorBasedWidget import com.intellij.util.Consumer import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.common.EditorListener import com.maddyhome.idea.vim.helper.EngineStringHelper import com.maddyhome.idea.vim.helper.VimNlsSafe import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.options.GlobalOptionChangeListener import org.jetbrains.annotations.NonNls import java.awt.Component import java.awt.event.MouseEvent internal object ShowCmd { // https://github.com/vim/vim/blob/b376ace1aeaa7614debc725487d75c8f756dd773/src/vim.h#L1721 private const val SHOWCMD_COLS = 10 @NonNls internal const val ID = "IdeaVimShowCmd" @NlsSafe internal const val displayName = "IdeaVim showcmd" fun update() { val windowManager = WindowManager.getInstance() ProjectManager.getInstance().openProjects.forEach { val statusBar = windowManager.getStatusBar(it) statusBar.updateWidget(ID) } } fun getWidgetText(editor: Editor?): String { // Vim only shows the last 10 characters. See normal.c:add_to_showcmd // https://github.com/vim/vim/blob/b376ace1aeaa7614debc725487d75c8f756dd773/src/normal.c#L1885-L1890 return getFullText(editor).takeLast(SHOWCMD_COLS) } fun getFullText(editor: Editor?): String { if (!injector.globalOptions().showcmd || editor == null || editor.isDisposed) return "" val keyState = KeyHandler.getInstance().keyHandlerState return EngineStringHelper.toPrintableCharacters(keyState.commandBuilder.keys + keyState.mappingState.keys) } } internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener { override fun onGlobalOptionChanged() { ShowCmd.update() } } internal class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory/*, LightEditCompatible*/ { override fun getId() = ShowCmd.ID override fun getDisplayName(): String = ShowCmd.displayName override fun disposeWidget(widget: StatusBarWidget) { // Nothing } override fun isAvailable(project: Project): Boolean { VimPlugin.getInstance() return injector.globalOptions().showcmd } override fun createWidget(project: Project): StatusBarWidget = Widget(project) override fun canBeEnabledOn(statusBar: StatusBar): Boolean = true // Should be configured via `set showcmd` override fun isConfigurable(): Boolean = false } // `:help 'showcmd'` // Widget shows: // * Partial command, as it's being typed // * When selecting characters within a line, the number of characters // * Tabs are shown as one char // * If the number of bytes is different, this is also shown: "2-6" // * When selecting more than one line, the number of lines // * When selecting a block, the size in screen characters: {lines}x{columns} // // We only need to show partial commands, since the standard PositionPanel shows the other information already, with // the exception of "{lines}x{columns}" (it shows "x carets" instead) internal class Widget(project: Project) : EditorBasedWidget(project), StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation, FileEditorManagerListener { override fun ID() = ShowCmd.ID override fun getPresentation(): StatusBarWidget.WidgetPresentation = this override fun getClickConsumer(): Consumer<MouseEvent>? = null @VimNlsSafe override fun getTooltipText(): String { var count = ShowCmd.getFullText(getEditor()) if (count.isNotBlank()) count = ": $count" return "${ShowCmd.displayName}$count" } override fun getText(): String = ShowCmd.getWidgetText(getEditor()) override fun getAlignment() = Component.CENTER_ALIGNMENT // Multiframe#copy to show the widget on popped out editors override fun copy(): StatusBarWidget = Widget(project) } internal class ShowCmdWidgetUpdater : EditorListener { override fun focusGained(editor: VimEditor) { editor.ij.project?.let { selectionChanged(it) } } override fun focusLost(editor: VimEditor) { editor.ij.project?.let { selectionChanged(it) } } private fun selectionChanged(project: Project) { val windowManager = WindowManager.getInstance() val statusBar = windowManager.getStatusBar(project) statusBar.updateWidget(ShowCmd.ID) } }