mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2026-04-11 18:57:41 +02:00
Compare commits
26 Commits
customized
...
customized
| Author | SHA1 | Date | |
|---|---|---|---|
|
078ddaf3ca
|
|||
|
94a7e1d303
|
|||
|
3de7743f56
|
|||
|
8636717dea
|
|||
|
22dfdd8ca6
|
|||
|
49f9f16f0d
|
|||
|
9bfc5d72ce
|
|||
|
84c227122a
|
|||
|
1b9ff4c94a
|
|||
|
bdecbb5ef0
|
|||
|
7dfd8e6cff
|
|||
|
31e76f0fcf
|
|||
|
2aadbdc8f0
|
|||
|
627d65e528
|
|||
|
e77871796e
|
|||
|
c6e993dcbd
|
|||
|
341ba1ba1f
|
|||
|
f3d7ad55f6
|
|||
|
5480b99898
|
|||
|
5734a13ea0
|
|||
|
582e6bdcd8
|
|||
|
7414c3d3ed
|
|||
|
8fa5bec363
|
|||
|
aea54bdf81
|
|||
|
79aca4497e
|
|||
|
50976ea9da
|
@@ -20,7 +20,7 @@ ideaVersion=2026.1
|
|||||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||||
ideaType=IU
|
ideaType=IU
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=chylex-55
|
version=chylex-56
|
||||||
javaVersion=21
|
javaVersion=21
|
||||||
remoteRobotVersion=0.11.23
|
remoteRobotVersion=0.11.23
|
||||||
antlrVersion=4.10.1
|
antlrVersion=4.10.1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
package com.maddyhome.idea.vim.newapi
|
package com.maddyhome.idea.vim.newapi
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.maddyhome.idea.vim.api.MessageType
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.project.ProjectManager
|
||||||
|
import com.intellij.openapi.wm.WindowManager
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimMessagesBase
|
import com.maddyhome.idea.vim.api.VimMessagesBase
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
@@ -23,43 +25,56 @@ internal class IjVimMessages : VimMessagesBase() {
|
|||||||
private var message: String? = null
|
private var message: String? = null
|
||||||
private var error = false
|
private var error = false
|
||||||
private var lastBeepTimeMillis = 0L
|
private var lastBeepTimeMillis = 0L
|
||||||
|
private var allowClearStatusBarMessage = true
|
||||||
|
|
||||||
override fun showMessage(editor: VimEditor, message: String?) {
|
override fun showStatusBarMessage(editor: VimEditor?, message: String?) {
|
||||||
showMessageInternal(editor, message, MessageType.STANDARD)
|
fun setStatusBarMessage(project: Project, message: String?) {
|
||||||
}
|
WindowManager.getInstance().getStatusBar(project)?.let {
|
||||||
|
it.info = if (message.isNullOrBlank()) "" else "Vim - $message"
|
||||||
override fun showErrorMessage(editor: VimEditor, message: String?) {
|
}
|
||||||
showMessageInternal(editor, message, MessageType.ERROR)
|
|
||||||
indicateError()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showMessageInternal(editor: VimEditor, message: String?, messageType: MessageType) {
|
|
||||||
this.message = message
|
|
||||||
|
|
||||||
if (message.isNullOrBlank()) {
|
|
||||||
clearStatusBarMessage()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = injector.executionContextManager.getEditorExecutionContext(editor)
|
this.message = message
|
||||||
injector.outputPanel.output(editor, context, message, messageType)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
val project = editor?.ij?.project
|
||||||
override fun showStatusBarMessage(editor: VimEditor?, message: String?) {
|
if (project != null) {
|
||||||
if (editor != null) {
|
setStatusBarMessage(project, message)
|
||||||
showMessage(editor, message)
|
|
||||||
} else {
|
} else {
|
||||||
// Legacy path for when editor is null - just store the message
|
// TODO: We really shouldn't set the status bar text for other projects. That's rude.
|
||||||
this.message = message
|
ProjectManager.getInstance().openProjects.forEach {
|
||||||
|
setStatusBarMessage(it, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw happens automatically based on changes or scrolling. If we've just set the message (e.g., searching for a
|
||||||
|
// string, hitting the bottom and scrolling to the top), make sure we don't immediately clear it when scrolling.
|
||||||
|
allowClearStatusBarMessage = false
|
||||||
|
ApplicationManager.getApplication().invokeLater {
|
||||||
|
allowClearStatusBarMessage = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatusBarMessage(): String? = message
|
override fun getStatusBarMessage(): String? = message
|
||||||
|
|
||||||
|
// Vim doesn't appear to have a policy about clearing the status bar, other than on "redraw". This can be forced with
|
||||||
|
// <C-L> or the `:redraw` command, but also happens as the screen changes, e.g., when inserting or deleting lines,
|
||||||
|
// scrolling, entering Command-line mode and probably lots more. We should manually clear the status bar when these
|
||||||
|
// things happen.
|
||||||
override fun clearStatusBarMessage() {
|
override fun clearStatusBarMessage() {
|
||||||
if (message.isNullOrEmpty()) return
|
val currentMessage = message
|
||||||
injector.outputPanel.getCurrentOutputPanel()?.close()
|
if (currentMessage.isNullOrEmpty()) return
|
||||||
|
|
||||||
|
// Don't clear the status bar message if we've only just set it
|
||||||
|
if (!allowClearStatusBarMessage) return
|
||||||
|
|
||||||
|
ProjectManager.getInstance().openProjects.forEach { project ->
|
||||||
|
WindowManager.getInstance().getStatusBar(project)?.let { statusBar ->
|
||||||
|
// Only clear the status bar if it's showing our last message
|
||||||
|
if (statusBar.info?.contains(currentMessage) == true) {
|
||||||
|
statusBar.info = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
message = null
|
message = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.intellij.openapi.wm.impl.IdeBackgroundUtil
|
import com.intellij.openapi.wm.impl.IdeBackgroundUtil
|
||||||
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
|
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
|
||||||
import com.intellij.ui.ClientProperty
|
import com.intellij.ui.ClientProperty
|
||||||
import com.intellij.ui.JBColor
|
|
||||||
import com.intellij.ui.components.JBPanel
|
import com.intellij.ui.components.JBPanel
|
||||||
import com.intellij.ui.components.JBScrollPane
|
import com.intellij.ui.components.JBScrollPane
|
||||||
import com.intellij.util.IJSwingUtilities
|
import com.intellij.util.IJSwingUtilities
|
||||||
@@ -25,6 +24,7 @@ import com.maddyhome.idea.vim.api.MessageType
|
|||||||
import com.maddyhome.idea.vim.api.VimOutputPanel
|
import com.maddyhome.idea.vim.api.VimOutputPanel
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.diagnostic.VimLogger
|
||||||
import com.maddyhome.idea.vim.helper.requestFocus
|
import com.maddyhome.idea.vim.helper.requestFocus
|
||||||
import com.maddyhome.idea.vim.helper.selectEditorFont
|
import com.maddyhome.idea.vim.helper.selectEditorFont
|
||||||
import com.maddyhome.idea.vim.helper.vimMorePanel
|
import com.maddyhome.idea.vim.helper.vimMorePanel
|
||||||
@@ -36,166 +36,121 @@ import java.awt.event.ComponentAdapter
|
|||||||
import java.awt.event.ComponentEvent
|
import java.awt.event.ComponentEvent
|
||||||
import java.awt.event.KeyAdapter
|
import java.awt.event.KeyAdapter
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.JLabel
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.JRootPane
|
||||||
import javax.swing.JScrollPane
|
import javax.swing.JScrollPane
|
||||||
import javax.swing.JTextPane
|
import javax.swing.JTextArea
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
import javax.swing.text.DefaultCaret
|
|
||||||
import javax.swing.text.SimpleAttributeSet
|
|
||||||
import javax.swing.text.StyleConstants
|
|
||||||
import javax.swing.text.StyledDocument
|
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel that displays text in a `more` like window overlaid on the editor.
|
* This panel displays text in a `more` like window and implements [VimOutputPanel].
|
||||||
*/
|
*/
|
||||||
class OutputPanel private constructor(
|
class OutputPanel(editorRef: WeakReference<Editor>) : JBPanel<OutputPanel?>(), VimOutputPanel {
|
||||||
private val editor: Editor,
|
private val myEditorRef: WeakReference<Editor> = editorRef
|
||||||
) : JBPanel<OutputPanel>(), VimOutputPanel {
|
val editor: Editor? get() = myEditorRef.get()
|
||||||
|
|
||||||
private val textPane = JTextPane()
|
val myLabel: JLabel = JLabel("more")
|
||||||
private val resizeAdapter: ComponentAdapter
|
private val myText = JTextArea()
|
||||||
private var defaultForeground: Color? = null
|
private val myScrollPane: JScrollPane =
|
||||||
|
JBScrollPane(myText, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
|
||||||
|
private val myAdapter: ComponentAdapter
|
||||||
|
private var myLineHeight = 0
|
||||||
|
|
||||||
private var glassPane: JComponent? = null
|
private var myOldGlass: JComponent? = null
|
||||||
private var originalLayout: LayoutManager? = null
|
private var myOldLayout: LayoutManager? = null
|
||||||
private var wasOpaque = false
|
private var myWasOpaque = false
|
||||||
|
|
||||||
var active: Boolean = false
|
var myActive: Boolean = false
|
||||||
private val segments = mutableListOf<TextLine>()
|
|
||||||
|
|
||||||
private val labelComponent: JLabel = JLabel("more")
|
val isActive: Boolean
|
||||||
private val scrollPane: JScrollPane =
|
get() = myActive
|
||||||
JBScrollPane(textPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
|
|
||||||
private var cachedLineHeight = 0
|
|
||||||
private var isSingleLine = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
textPane.isEditable = false
|
// Create a text editor for the text and a label for the prompt
|
||||||
textPane.caret = object : DefaultCaret() {
|
val layout = BorderLayout(0, 0)
|
||||||
override fun setVisible(v: Boolean) {
|
setLayout(layout)
|
||||||
super.setVisible(false)
|
add(myScrollPane, BorderLayout.CENTER)
|
||||||
}
|
add(myLabel, BorderLayout.SOUTH)
|
||||||
}
|
|
||||||
textPane.highlighter = null
|
|
||||||
|
|
||||||
resizeAdapter = object : ComponentAdapter() {
|
// Set the text area read only, and support wrap
|
||||||
|
myText.isEditable = false
|
||||||
|
myText.setLineWrap(true)
|
||||||
|
|
||||||
|
myAdapter = object : ComponentAdapter() {
|
||||||
override fun componentResized(e: ComponentEvent?) {
|
override fun componentResized(e: ComponentEvent?) {
|
||||||
positionPanel()
|
positionPanel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suppress the fancy frame background used in the Islands theme
|
// Setup some listeners to handle keystrokes
|
||||||
ClientProperty.putRecursive(this, IdeBackgroundUtil.NO_BACKGROUND, true)
|
val moreKeyListener = MoreKeyListener()
|
||||||
putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, editor.component)
|
addKeyListener(moreKeyListener)
|
||||||
|
myText.addKeyListener(moreKeyListener)
|
||||||
|
|
||||||
// Initialize panel
|
// Suppress the fancy frame background used in the Islands theme, which comes from a custom Graphics implementation
|
||||||
setLayout(BorderLayout(0, 0))
|
// applied to the IdeRoot, and used to paint all children, including this panel. This client property is checked by
|
||||||
add(scrollPane, BorderLayout.CENTER)
|
// JBPanel.getComponentGraphics to give us the original Graphics, opting out of the fancy painting.
|
||||||
add(labelComponent, BorderLayout.SOUTH)
|
ClientProperty.putRecursive<Boolean?>(this, IdeBackgroundUtil.NO_BACKGROUND, true)
|
||||||
|
editor?.let { putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, it.component) }
|
||||||
val keyListener = OutputPanelKeyListener()
|
|
||||||
addKeyListener(keyListener)
|
|
||||||
textPane.addKeyListener(keyListener)
|
|
||||||
|
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler
|
||||||
override fun updateUI() {
|
override fun updateUI() {
|
||||||
super.updateUI()
|
super.updateUI()
|
||||||
|
|
||||||
setBorder(ExPanelBorder())
|
setBorder(ExPanelBorder())
|
||||||
|
|
||||||
|
// Swing uses a bad pattern of calling updateUI() from the constructor. At this moment, all these variables are null
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
@Suppress("SENSELESS_COMPARISON")
|
||||||
if (textPane != null && labelComponent != null && scrollPane != null) {
|
if (myText != null && myLabel != null && myScrollPane != null) {
|
||||||
setFontForElements()
|
setFontForElements()
|
||||||
textPane.setBorder(null)
|
myText.setBorder(null)
|
||||||
scrollPane.setBorder(null)
|
myScrollPane.setBorder(null)
|
||||||
labelComponent.setForeground(textPane.getForeground())
|
myLabel.setForeground(myText.getForeground())
|
||||||
|
|
||||||
|
// Make sure the panel is positioned correctly in case we're changing font size
|
||||||
positionPanel()
|
positionPanel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var text: String
|
override var text: String
|
||||||
get() = textPane.getText() ?: ""
|
get() = myText.text
|
||||||
set(value) {
|
set(value) {
|
||||||
|
// ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour.
|
||||||
val newValue = value.removeSuffix("\n")
|
val newValue = value.removeSuffix("\n")
|
||||||
segments.clear()
|
myText.text = newValue
|
||||||
if (newValue.isEmpty()) return
|
val ed = editor
|
||||||
segments.add(TextLine(newValue, null))
|
if (ed != null) {
|
||||||
|
myText.setFont(selectEditorFont(ed, newValue))
|
||||||
|
}
|
||||||
|
myText.setCaretPosition(0)
|
||||||
|
if (newValue.isNotEmpty()) {
|
||||||
|
activate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var label: String
|
override var label: String
|
||||||
get() = labelComponent.text
|
get() = myLabel.text ?: ""
|
||||||
set(value) {
|
set(value) {
|
||||||
labelComponent.text = value
|
myLabel.text = value
|
||||||
|
val ed = editor
|
||||||
|
if (ed != null) {
|
||||||
|
myLabel.setFont(selectEditorFont(ed, value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets styled text with multiple segments, each potentially having a different color.
|
|
||||||
*/
|
|
||||||
fun setStyledText(lines: List<TextLine>) {
|
|
||||||
val doc = textPane.styledDocument
|
|
||||||
doc.remove(0, doc.length)
|
|
||||||
|
|
||||||
if (defaultForeground == null) {
|
|
||||||
defaultForeground = textPane.foreground
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lines.size > 1) {
|
|
||||||
setMultiLineText(lines, doc)
|
|
||||||
} else {
|
|
||||||
doc.insertString(doc.length, lines[0].text.removeSuffix("\n"), getLineColor(lines[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
val fullText = doc.getText(0, doc.length)
|
|
||||||
textPane.setFont(selectEditorFont(editor, fullText))
|
|
||||||
textPane.setCaretPosition(0)
|
|
||||||
if (fullText.isNotEmpty()) {
|
|
||||||
activate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setMultiLineText(
|
|
||||||
lines: List<TextLine>,
|
|
||||||
doc: StyledDocument,
|
|
||||||
) {
|
|
||||||
for ((index, line) in lines.withIndex()) {
|
|
||||||
val text = line.text.removeSuffix("\n")
|
|
||||||
val attrs = getLineColor(line)
|
|
||||||
val separator = if (index < lines.size - 1) "\n" else ""
|
|
||||||
doc.insertString(doc.length, text + separator, attrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLineColor(segment: TextLine): SimpleAttributeSet {
|
|
||||||
val attrs = SimpleAttributeSet()
|
|
||||||
val color = segment.color ?: defaultForeground
|
|
||||||
if (color != null) {
|
|
||||||
StyleConstants.setForeground(attrs, color)
|
|
||||||
}
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addText(text: String, isNewLine: Boolean, messageType: MessageType) {
|
override fun addText(text: String, isNewLine: Boolean, messageType: MessageType) {
|
||||||
val color = when (messageType) {
|
if (this.text.isNotEmpty() && isNewLine) {
|
||||||
MessageType.ERROR -> JBColor.RED
|
this.text += "\n$text"
|
||||||
MessageType.STANDARD -> null
|
} else {
|
||||||
}
|
this.text += text
|
||||||
segments.add(TextLine(text, color))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun show() {
|
|
||||||
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
|
|
||||||
if (currentPanel != null && currentPanel != this) currentPanel.close()
|
|
||||||
|
|
||||||
setStyledText(segments)
|
|
||||||
if (!active) {
|
|
||||||
activate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,15 +159,20 @@ class OutputPanel private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun clearText() {
|
override fun clearText() {
|
||||||
segments.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleKey(key: KeyStroke) {
|
override fun show() {
|
||||||
|
editor ?: return
|
||||||
|
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
|
||||||
|
if (currentPanel != null && currentPanel != this) currentPanel.close()
|
||||||
|
|
||||||
|
if (!myActive) {
|
||||||
|
activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleKey(key: KeyStroke) {
|
||||||
if (isAtEnd) {
|
if (isAtEnd) {
|
||||||
close(key)
|
close(key)
|
||||||
return
|
return
|
||||||
@@ -237,262 +197,214 @@ class OutputPanel private constructor(
|
|||||||
|
|
||||||
override fun getForeground(): Color? {
|
override fun getForeground(): Color? {
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
@Suppress("SENSELESS_COMPARISON")
|
||||||
if (textPane == null) {
|
if (myText == null) {
|
||||||
|
// Swing uses a bad pattern of calling getForeground() from the constructor. At this moment, `myText` is null.
|
||||||
return super.getForeground()
|
return super.getForeground()
|
||||||
}
|
}
|
||||||
return textPane.getForeground()
|
return myText.getForeground()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBackground(): Color? {
|
override fun getBackground(): Color? {
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
@Suppress("SENSELESS_COMPARISON")
|
||||||
if (textPane == null) {
|
if (myText == null) {
|
||||||
|
// Swing uses a bad pattern of calling getBackground() from the constructor. At this moment, `myText` is null.
|
||||||
return super.getBackground()
|
return super.getBackground()
|
||||||
}
|
}
|
||||||
return textPane.getBackground()
|
return myText.getBackground()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns off the output panel and optionally puts the focus back to the original component.
|
* Turns off the ex entry field and optionally puts the focus back to the original component
|
||||||
*/
|
*/
|
||||||
fun deactivate(refocusOwningEditor: Boolean) {
|
fun deactivate(refocusOwningEditor: Boolean) {
|
||||||
if (!active) return
|
if (!myActive) return
|
||||||
active = false
|
myActive = false
|
||||||
clearText()
|
myText.text = ""
|
||||||
textPane.text = ""
|
val ed = editor
|
||||||
if (refocusOwningEditor) {
|
if (refocusOwningEditor && ed != null) {
|
||||||
requestFocus(editor.contentComponent)
|
requestFocus(ed.contentComponent)
|
||||||
}
|
}
|
||||||
if (glassPane != null) {
|
if (myOldGlass != null) {
|
||||||
glassPane!!.removeComponentListener(resizeAdapter)
|
myOldGlass!!.removeComponentListener(myAdapter)
|
||||||
glassPane!!.isVisible = false
|
myOldGlass!!.isVisible = false
|
||||||
glassPane!!.remove(this)
|
myOldGlass!!.remove(this)
|
||||||
glassPane!!.setOpaque(wasOpaque)
|
myOldGlass!!.setOpaque(myWasOpaque)
|
||||||
glassPane!!.setLayout(originalLayout)
|
myOldGlass!!.setLayout(myOldLayout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns on the output panel for the given editor.
|
* Turns on the more window for the given editor
|
||||||
*/
|
*/
|
||||||
fun activate() {
|
fun activate() {
|
||||||
disableOldGlass()
|
val ed = editor ?: return
|
||||||
|
val root = SwingUtilities.getRootPane(ed.contentComponent)
|
||||||
|
deactivateOldGlass(root)
|
||||||
|
|
||||||
setFontForElements()
|
setFontForElements()
|
||||||
positionPanel()
|
positionPanel()
|
||||||
|
|
||||||
if (glassPane != null) {
|
if (myOldGlass != null) {
|
||||||
glassPane!!.isVisible = true
|
myOldGlass!!.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
active = true
|
myActive = true
|
||||||
requestFocus(textPane)
|
requestFocus(myText)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun disableOldGlass() {
|
private fun deactivateOldGlass(root: JRootPane?) {
|
||||||
val root = SwingUtilities.getRootPane(editor.contentComponent) ?: return
|
if (root == null) return
|
||||||
glassPane = root.getGlassPane() as JComponent?
|
myOldGlass = root.getGlassPane() as JComponent?
|
||||||
if (glassPane == null) {
|
if (myOldGlass != null) {
|
||||||
|
myOldLayout = myOldGlass!!.layout
|
||||||
|
myWasOpaque = myOldGlass!!.isOpaque
|
||||||
|
myOldGlass!!.setLayout(null)
|
||||||
|
myOldGlass!!.setOpaque(false)
|
||||||
|
myOldGlass!!.add(this)
|
||||||
|
myOldGlass!!.addComponentListener(myAdapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setFontForElements() {
|
||||||
|
val ed = editor ?: return
|
||||||
|
myText.setFont(selectEditorFont(ed, myText.getText()))
|
||||||
|
myLabel.setFont(selectEditorFont(ed, myLabel.text))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun scrollLine() {
|
||||||
|
scrollOffset(myLineHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun scrollPage() {
|
||||||
|
scrollOffset(myScrollPane.getVerticalScrollBar().visibleAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun scrollHalfPage() {
|
||||||
|
val sa = myScrollPane.getVerticalScrollBar().visibleAmount / 2.0
|
||||||
|
val offset = ceil(sa / myLineHeight) * myLineHeight
|
||||||
|
scrollOffset(offset.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBadKey() {
|
||||||
|
val ed = editor ?: return
|
||||||
|
myLabel.setText(injector.messages.message("message.ex.output.more.prompt.full"))
|
||||||
|
myLabel.setFont(selectEditorFont(ed, myLabel.text))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollOffset(more: Int) {
|
||||||
|
val ed = editor ?: return
|
||||||
|
val `val` = myScrollPane.getVerticalScrollBar().value
|
||||||
|
myScrollPane.getVerticalScrollBar().setValue(`val` + more)
|
||||||
|
myScrollPane.getHorizontalScrollBar().setValue(0)
|
||||||
|
if (isAtEnd) {
|
||||||
|
myLabel.setText(injector.messages.message("message.ex.output.end.prompt"))
|
||||||
|
} else {
|
||||||
|
myLabel.setText(injector.messages.message("message.ex.output.more.prompt"))
|
||||||
|
}
|
||||||
|
myLabel.setFont(selectEditorFont(ed, myLabel.text))
|
||||||
|
}
|
||||||
|
|
||||||
|
val isAtEnd: Boolean
|
||||||
|
get() {
|
||||||
|
val isSingleLine = myText.getLineCount() == 1
|
||||||
|
if (isSingleLine) return true
|
||||||
|
val scrollBar = myScrollPane.getVerticalScrollBar()
|
||||||
|
val value = scrollBar.value
|
||||||
|
if (!scrollBar.isVisible) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return value >= scrollBar.maximum - scrollBar.visibleAmount ||
|
||||||
|
scrollBar.maximum <= scrollBar.visibleAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun positionPanel() {
|
||||||
|
val ed = editor ?: return
|
||||||
|
val contentComponent = ed.contentComponent
|
||||||
|
val scroll = SwingUtilities.getAncestorOfClass(JScrollPane::class.java, contentComponent)
|
||||||
|
val rootPane = SwingUtilities.getRootPane(contentComponent)
|
||||||
|
if (scroll == null || rootPane == null) {
|
||||||
|
// These might be null if we're invoked during component initialisation and before it's been added to the tree
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
originalLayout = glassPane!!.layout
|
|
||||||
wasOpaque = glassPane!!.isOpaque
|
size = scroll.size
|
||||||
glassPane!!.setLayout(null)
|
|
||||||
glassPane!!.setOpaque(false)
|
myLineHeight = myText.getFontMetrics(myText.getFont()).height
|
||||||
glassPane!!.add(this)
|
val count: Int = countLines(myText.getText())
|
||||||
glassPane!!.addComponentListener(resizeAdapter)
|
val visLines = size.height / myLineHeight - 1
|
||||||
|
val lines = min(count, visLines)
|
||||||
|
setSize(
|
||||||
|
size.width,
|
||||||
|
lines * myLineHeight + myLabel.getPreferredSize().height + border.getBorderInsets(this).top * 2
|
||||||
|
)
|
||||||
|
|
||||||
|
val height = size.height
|
||||||
|
val bounds = scroll.bounds
|
||||||
|
bounds.translate(0, scroll.getHeight() - height)
|
||||||
|
bounds.height = height
|
||||||
|
val pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.location, rootPane.getGlassPane())
|
||||||
|
bounds.location = pos
|
||||||
|
setBounds(bounds)
|
||||||
|
|
||||||
|
myScrollPane.getVerticalScrollBar().setValue(0)
|
||||||
|
if (!injector.globalOptions().more) {
|
||||||
|
// FIX
|
||||||
|
scrollOffset(100000)
|
||||||
|
} else {
|
||||||
|
scrollOffset(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close(key: KeyStroke? = null) {
|
||||||
|
val ed = editor ?: return
|
||||||
|
ApplicationManager.getApplication().invokeLater {
|
||||||
|
deactivate(true)
|
||||||
|
val project = ed.project
|
||||||
|
if (project != null && key != null && key.keyChar != '\n') {
|
||||||
|
val keys: MutableList<KeyStroke> = ArrayList(1)
|
||||||
|
keys.add(key)
|
||||||
|
if (LOG.isTrace()) {
|
||||||
|
LOG.trace(
|
||||||
|
"Adding new keys to keyStack as part of playback. State before adding keys: " +
|
||||||
|
getInstance().keyStack.dump()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
getInstance().keyStack.addKeys(keys)
|
||||||
|
val context: ExecutionContext =
|
||||||
|
injector.executionContextManager.getEditorExecutionContext(IjVimEditor(ed))
|
||||||
|
VimPlugin.getMacro().playbackKeys(IjVimEditor(ed), context, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
close(null)
|
close(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun close(key: KeyStroke?) {
|
private class MoreKeyListener : KeyAdapter() {
|
||||||
val passKeyBack = isSingleLine
|
/**
|
||||||
ApplicationManager.getApplication().invokeLater {
|
* Invoked when a key has been pressed.
|
||||||
deactivate(true)
|
*/
|
||||||
val project = editor.project
|
|
||||||
// For single line messages, pass any key back to the editor (including Enter)
|
|
||||||
// For multi-line messages, don't pass Enter back (it was used to dismiss)
|
|
||||||
if (project != null && key != null && (passKeyBack || key.keyChar != '\n')) {
|
|
||||||
val keys: MutableList<KeyStroke> = ArrayList(1)
|
|
||||||
keys.add(key)
|
|
||||||
getInstance().keyStack.addKeys(keys)
|
|
||||||
val context: ExecutionContext =
|
|
||||||
injector.executionContextManager.getEditorExecutionContext(IjVimEditor(editor))
|
|
||||||
VimPlugin.getMacro().playbackKeys(IjVimEditor(editor), context, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setFontForElements() {
|
|
||||||
textPane.setFont(selectEditorFont(editor, textPane.getText()))
|
|
||||||
labelComponent.setFont(selectEditorFont(editor, labelComponent.text))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun positionPanel() {
|
|
||||||
val scroll = positionPanelStart() ?: return
|
|
||||||
val lineHeight = textPane.getFontMetrics(textPane.getFont()).height
|
|
||||||
val count = countLines(textPane.getText())
|
|
||||||
val visLines = size.height / lineHeight - 1
|
|
||||||
val lines = min(count, visLines)
|
|
||||||
|
|
||||||
// Simple output: single line that fits entirely - no label needed
|
|
||||||
isSingleLine = count == 1 && count <= visLines
|
|
||||||
labelComponent.isVisible = !isSingleLine
|
|
||||||
|
|
||||||
val extraHeight = if (isSingleLine) 0 else labelComponent.getPreferredSize().height
|
|
||||||
setSize(
|
|
||||||
size.width,
|
|
||||||
lines * lineHeight + extraHeight + border.getBorderInsets(this).top * 2
|
|
||||||
)
|
|
||||||
|
|
||||||
finishPositioning(scroll)
|
|
||||||
|
|
||||||
// Force layout so that viewport sizes are valid before checking scroll state
|
|
||||||
validate()
|
|
||||||
|
|
||||||
// onPositioned
|
|
||||||
cachedLineHeight = lineHeight
|
|
||||||
scrollPane.getVerticalScrollBar().setValue(0)
|
|
||||||
if (!isSingleLine) {
|
|
||||||
if (!injector.globalOptions().more) {
|
|
||||||
scrollOffset(100000)
|
|
||||||
} else {
|
|
||||||
scrollOffset(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun positionPanelStart(): JScrollPane? {
|
|
||||||
val contentComponent = editor.contentComponent
|
|
||||||
val scroll = SwingUtilities.getAncestorOfClass(JScrollPane::class.java, contentComponent) as? JScrollPane
|
|
||||||
val rootPane = SwingUtilities.getRootPane(contentComponent)
|
|
||||||
if (scroll == null || rootPane == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
size = scroll.size
|
|
||||||
return scroll
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun finishPositioning(scroll: JScrollPane) {
|
|
||||||
val rootPane = SwingUtilities.getRootPane(editor.contentComponent)
|
|
||||||
val bounds = scroll.bounds
|
|
||||||
bounds.translate(0, scroll.getHeight() - size.height)
|
|
||||||
bounds.height = size.height
|
|
||||||
val pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.location, rootPane.getGlassPane())
|
|
||||||
bounds.location = pos
|
|
||||||
setBounds(bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun countLines(text: String): Int {
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = 0
|
|
||||||
var pos = -1
|
|
||||||
while ((text.indexOf('\n', pos + 1).also { pos = it }) != -1) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text[text.length - 1] != '\n') {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun scrollLine() {
|
|
||||||
scrollOffset(cachedLineHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun scrollPage() {
|
|
||||||
scrollOffset(scrollPane.getVerticalScrollBar().visibleAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun scrollHalfPage() {
|
|
||||||
val sa = scrollPane.getVerticalScrollBar().visibleAmount / 2.0
|
|
||||||
val offset = ceil(sa / cachedLineHeight) * cachedLineHeight
|
|
||||||
scrollOffset(offset.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onBadKey() {
|
|
||||||
labelComponent.setText(injector.messages.message("message.ex.output.more.prompt.full"))
|
|
||||||
labelComponent.setFont(selectEditorFont(editor, labelComponent.text))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scrollOffset(more: Int) {
|
|
||||||
scrollPane.validate()
|
|
||||||
val scrollBar = scrollPane.getVerticalScrollBar()
|
|
||||||
val value = scrollBar.value
|
|
||||||
scrollBar.setValue(value + more)
|
|
||||||
scrollPane.getHorizontalScrollBar().setValue(0)
|
|
||||||
|
|
||||||
// Check if we're at the end or if content fits entirely (nothing to scroll)
|
|
||||||
if (isAtEnd) {
|
|
||||||
labelComponent.setText(injector.messages.message("message.ex.output.end.prompt"))
|
|
||||||
} else {
|
|
||||||
labelComponent.setText(injector.messages.message("message.ex.output.more.prompt"))
|
|
||||||
}
|
|
||||||
labelComponent.setFont(selectEditorFont(editor, labelComponent.text))
|
|
||||||
}
|
|
||||||
|
|
||||||
val isAtEnd: Boolean
|
|
||||||
get() {
|
|
||||||
if (isSingleLine) return true
|
|
||||||
val contentHeight = textPane.preferredSize.height
|
|
||||||
val viewportHeight = scrollPane.viewport.height
|
|
||||||
if (contentHeight <= viewportHeight) return true
|
|
||||||
val scrollBar = scrollPane.getVerticalScrollBar()
|
|
||||||
return scrollBar.value >= scrollBar.maximum - scrollBar.visibleAmount
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class OutputPanelKeyListener : KeyAdapter() {
|
|
||||||
override fun keyTyped(e: KeyEvent) {
|
override fun keyTyped(e: KeyEvent) {
|
||||||
val currentPanel: VimOutputPanel = injector.outputPanel.getCurrentOutputPanel() ?: return
|
val currentPanel: VimOutputPanel = injector.outputPanel.getCurrentOutputPanel() ?: return
|
||||||
|
|
||||||
val keyChar = e.keyChar
|
val keyCode = e.getKeyCode()
|
||||||
|
val keyChar = e.getKeyChar()
|
||||||
val modifiers = e.modifiersEx
|
val modifiers = e.modifiersEx
|
||||||
val keyStroke = KeyStroke.getKeyStroke(keyChar, modifiers)
|
val keyStroke = if (keyChar == KeyEvent.CHAR_UNDEFINED)
|
||||||
|
KeyStroke.getKeyStroke(keyCode, modifiers)
|
||||||
|
else
|
||||||
|
KeyStroke.getKeyStroke(keyChar, modifiers)
|
||||||
currentPanel.handleKey(keyStroke)
|
currentPanel.handleKey(keyStroke)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun keyPressed(e: KeyEvent) {
|
|
||||||
if (!e.isActionKey && e.keyCode != KeyEvent.VK_ENTER) return
|
|
||||||
val currentPanel = injector.outputPanel.getCurrentOutputPanel() as? OutputPanel ?: return
|
|
||||||
|
|
||||||
val keyCode = e.keyCode
|
|
||||||
val modifiers = e.modifiersEx
|
|
||||||
val keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers)
|
|
||||||
|
|
||||||
if (isSingleLine) {
|
|
||||||
currentPanel.close(keyStroke)
|
|
||||||
e.consume()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-line mode: arrow keys scroll, down/right at end closes
|
|
||||||
when (keyCode) {
|
|
||||||
KeyEvent.VK_ENTER -> {
|
|
||||||
if (currentPanel.isAtEnd) currentPanel.close() else currentPanel.scrollLine()
|
|
||||||
e.consume()
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyEvent.VK_DOWN -> if (currentPanel.isAtEnd) currentPanel.close(keyStroke) else currentPanel.scrollLine()
|
|
||||||
KeyEvent.VK_RIGHT -> if (currentPanel.isAtEnd) currentPanel.close(keyStroke) else currentPanel.scrollLine()
|
|
||||||
KeyEvent.VK_UP -> currentPanel.scrollOffset(-cachedLineHeight)
|
|
||||||
KeyEvent.VK_LEFT -> currentPanel.scrollOffset(-cachedLineHeight)
|
|
||||||
KeyEvent.VK_PAGE_DOWN -> if (currentPanel.isAtEnd) currentPanel.close(keyStroke) else currentPanel.scrollPage()
|
|
||||||
KeyEvent.VK_PAGE_UP -> currentPanel.scrollOffset(-scrollPane.verticalScrollBar.visibleAmount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LafListener : LafManagerListener {
|
class LafListener : LafManagerListener {
|
||||||
override fun lookAndFeelChanged(source: LafManager) {
|
override fun lookAndFeelChanged(source: LafManager) {
|
||||||
if (VimPlugin.isNotEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
|
// This listener is only invoked for local scenarios, and we only need to update local editor UI. This will invoke
|
||||||
|
// updateUI on the output pane and it's child components
|
||||||
for (vimEditor in injector.editorGroup.getEditors()) {
|
for (vimEditor in injector.editorGroup.getEditors()) {
|
||||||
val editor = (vimEditor as IjVimEditor).editor
|
val editor = (vimEditor as IjVimEditor).editor
|
||||||
if (!isPanelActive(editor)) continue
|
if (!isPanelActive(editor)) continue
|
||||||
@@ -502,24 +414,41 @@ class OutputPanel private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val LOG: VimLogger = injector.getLogger<OutputPanel>(OutputPanel::class.java)
|
||||||
|
|
||||||
fun getNullablePanel(editor: Editor): OutputPanel? {
|
fun getNullablePanel(editor: Editor): OutputPanel? {
|
||||||
return editor.vimMorePanel as OutputPanel?
|
return editor.vimMorePanel as? OutputPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPanelActive(editor: Editor): Boolean {
|
fun isPanelActive(editor: Editor): Boolean {
|
||||||
return getNullablePanel(editor) != null
|
return getNullablePanel(editor)?.myActive ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInstance(editor: Editor): OutputPanel {
|
fun getInstance(editor: Editor): OutputPanel {
|
||||||
var panel: OutputPanel? = getNullablePanel(editor)
|
var panel: OutputPanel? = getNullablePanel(editor)
|
||||||
if (panel == null) {
|
if (panel == null) {
|
||||||
panel = OutputPanel(editor)
|
panel = OutputPanel(WeakReference(editor))
|
||||||
editor.vimMorePanel = panel
|
editor.vimMorePanel = panel
|
||||||
}
|
}
|
||||||
return panel
|
return panel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun countLines(text: String): Int {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
var pos = -1
|
||||||
|
while ((text.indexOf('\n', pos + 1).also { pos = it }) != -1) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text[text.length - 1] != '\n') {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class TextLine(val text: String, val color: Color?)
|
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ class IjOutputPanelService : VimOutputPanelServiceBase() {
|
|||||||
private var activeOutputPanel: WeakReference<VimOutputPanel>? = null
|
private var activeOutputPanel: WeakReference<VimOutputPanel>? = null
|
||||||
|
|
||||||
override fun getCurrentOutputPanel(): VimOutputPanel? {
|
override fun getCurrentOutputPanel(): VimOutputPanel? {
|
||||||
return activeOutputPanel?.get()?.takeIf { (it as OutputPanel).active }
|
return activeOutputPanel?.get()?.takeIf { (it as OutputPanel).isActive }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun create(editor: VimEditor, context: ExecutionContext): VimOutputPanel {
|
override fun create(editor: VimEditor, context: ExecutionContext): VimOutputPanel {
|
||||||
val panel = OutputPanel.getInstance(editor.ij)
|
val panel = OutputPanel(WeakReference(editor.ij))
|
||||||
activeOutputPanel = WeakReference(panel)
|
activeOutputPanel = WeakReference(panel)
|
||||||
return panel
|
return panel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.maddyhome.idea.vim.vimscript.model.functions.handlers
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.ActionManager
|
||||||
|
import com.intellij.openapi.actionSystem.impl.PresentationFactory
|
||||||
|
import com.intellij.openapi.actionSystem.impl.Utils
|
||||||
|
import com.intellij.openapi.keymap.impl.ActionProcessor
|
||||||
|
import com.intellij.vim.annotations.VimscriptFunction
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.asVimInt
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.functions.BuiltinFunctionHandler
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
|
||||||
|
@VimscriptFunction(name = "isactionenabled")
|
||||||
|
internal class IsActionEnabled : BuiltinFunctionHandler<VimInt>() {
|
||||||
|
override fun doFunction(
|
||||||
|
arguments: Arguments,
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
vimContext: VimLContext,
|
||||||
|
): VimInt {
|
||||||
|
val action = ActionManager.getInstance().getAction(arguments.getString(0).value)
|
||||||
|
if (action == null) {
|
||||||
|
return false.asVimInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
val presentationFactory = PresentationFactory()
|
||||||
|
val wrappedContext = Utils.createAsyncDataContext(context.ij)
|
||||||
|
val actionProcessor = object : ActionProcessor() {}
|
||||||
|
val inputEventAdjusted = KeyEvent(editor.ij.contentComponent, KeyEvent.KEY_PRESSED, 0L, 0, KeyEvent.VK_UNDEFINED, '\u0000')
|
||||||
|
|
||||||
|
val updateEvent = Utils.runUpdateSessionForInputEvent(listOf(action), inputEventAdjusted, wrappedContext, "IdeaVim", actionProcessor, presentationFactory) { _, updater, events ->
|
||||||
|
val presentation = updater(action)
|
||||||
|
events[presentation]
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = updateEvent != null && updateEvent.presentation.isEnabled
|
||||||
|
return result.asVimInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler",
|
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler",
|
||||||
|
"isactionenabled": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.IsActionEnabled",
|
||||||
"pumvisible": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.PopupMenuVisibleFunctionHandler"
|
"pumvisible": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.PopupMenuVisibleFunctionHandler"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -134,7 +134,7 @@ class CmdCommandTest : VimTestCase() {
|
|||||||
VimPlugin.getCommand().resetAliases()
|
VimPlugin.getCommand().resetAliases()
|
||||||
configureByText("\n")
|
configureByText("\n")
|
||||||
typeText(commandToKeys("command! -range Error echo <args>"))
|
typeText(commandToKeys("command! -range Error echo <args>"))
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
kotlin.test.assertEquals("'-range' is not supported by `command`", injector.messages.getStatusBarMessage())
|
kotlin.test.assertEquals("'-range' is not supported by `command`", injector.messages.getStatusBarMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ class CmdCommandTest : VimTestCase() {
|
|||||||
VimPlugin.getCommand().resetAliases()
|
VimPlugin.getCommand().resetAliases()
|
||||||
configureByText("\n")
|
configureByText("\n")
|
||||||
typeText(commandToKeys("command! -complete=color Error echo <args>"))
|
typeText(commandToKeys("command! -complete=color Error echo <args>"))
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
kotlin.test.assertEquals("'-complete' is not supported by `command`", injector.messages.getStatusBarMessage())
|
kotlin.test.assertEquals("'-complete' is not supported by `command`", injector.messages.getStatusBarMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -24,6 +24,7 @@ class ExecuteCommandTest : VimTestCase() {
|
|||||||
fun `test execute with range`() {
|
fun `test execute with range`() {
|
||||||
configureByText("\n")
|
configureByText("\n")
|
||||||
typeText(commandToKeys("1,2execute 'echo 42'"))
|
typeText(commandToKeys("1,2execute 'echo 42'"))
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class HistoryCommandTest : VimTestCase() {
|
|||||||
fun `test history with 'history' option set to 0 shows nothing`() {
|
fun `test history with 'history' option set to 0 shows nothing`() {
|
||||||
enterCommand("set history=0")
|
enterCommand("set history=0")
|
||||||
enterCommand("history")
|
enterCommand("history")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(false)
|
assertPluginError(false)
|
||||||
assertPluginErrorMessage("'history' option is zero")
|
assertPluginErrorMessage("'history' option is zero")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2025 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -43,6 +43,7 @@ class AndFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test and function with list causes error`() {
|
fun `test and function with list causes error`() {
|
||||||
enterCommand("echo and([1, 2, 3], [2, 3, 4])")
|
enterCommand("echo and([1, 2, 3], [2, 3, 4])")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||||
}
|
}
|
||||||
@@ -50,6 +51,7 @@ class AndFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test and function with dict causes error`() {
|
fun `test and function with dict causes error`() {
|
||||||
enterCommand("echo and({1: 2, 3: 4}, {3: 4, 5: 6})")
|
enterCommand("echo and({1: 2, 3: 4}, {3: 4, 5: 6})")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,7 @@ class AndFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test and function with float causes error`() {
|
fun `test and function with float causes error`() {
|
||||||
enterCommand("echo and(1.5, 2.5)")
|
enterCommand("echo and(1.5, 2.5)")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2025 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -28,6 +28,7 @@ class InvertFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test invert function with list causes error`() {
|
fun `test invert function with list causes error`() {
|
||||||
enterCommand("echo invert([1, 2, 3])")
|
enterCommand("echo invert([1, 2, 3])")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||||
}
|
}
|
||||||
@@ -35,6 +36,7 @@ class InvertFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test invert function with dict causes error`() {
|
fun `test invert function with dict causes error`() {
|
||||||
enterCommand("echo invert({1: 2, 3: 4})")
|
enterCommand("echo invert({1: 2, 3: 4})")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,7 @@ class InvertFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test invert function with float causes error`() {
|
fun `test invert function with float causes error`() {
|
||||||
enterCommand("echo invert(1.5)")
|
enterCommand("echo invert(1.5)")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2025 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -43,6 +43,7 @@ class OrFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test or function with list causes error`() {
|
fun `test or function with list causes error`() {
|
||||||
enterCommand("echo or([1, 2, 3], [2, 3, 4])")
|
enterCommand("echo or([1, 2, 3], [2, 3, 4])")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||||
}
|
}
|
||||||
@@ -50,6 +51,7 @@ class OrFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test or function with dict causes error`() {
|
fun `test or function with dict causes error`() {
|
||||||
enterCommand("echo or({1: 2, 3: 4}, {3: 4, 5: 6})")
|
enterCommand("echo or({1: 2, 3: 4}, {3: 4, 5: 6})")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,7 @@ class OrFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test or function with float causes error`() {
|
fun `test or function with float causes error`() {
|
||||||
enterCommand("echo or(1.5, 2.5)")
|
enterCommand("echo or(1.5, 2.5)")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2025 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -43,6 +43,7 @@ class XorFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test xor function with list causes error`() {
|
fun `test xor function with list causes error`() {
|
||||||
enterCommand("echo xor([1, 2, 3], [2, 3, 4])")
|
enterCommand("echo xor([1, 2, 3], [2, 3, 4])")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||||
}
|
}
|
||||||
@@ -50,6 +51,7 @@ class XorFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test xor function with dict causes error`() {
|
fun `test xor function with dict causes error`() {
|
||||||
enterCommand("echo xor({1: 2, 3: 4}, {3: 4, 5: 6})")
|
enterCommand("echo xor({1: 2, 3: 4}, {3: 4, 5: 6})")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,7 @@ class XorFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test xor function with float causes error`() {
|
fun `test xor function with float causes error`() {
|
||||||
enterCommand("echo xor(1.5, 2.5)")
|
enterCommand("echo xor(1.5, 2.5)")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2025 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -33,6 +33,7 @@ class ToLowerFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test tolower with list causes error`() {
|
fun `test tolower with list causes error`() {
|
||||||
enterCommand("echo tolower([1, 2, 3])")
|
enterCommand("echo tolower([1, 2, 3])")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E730: Using a List as a String")
|
assertPluginErrorMessage("E730: Using a List as a String")
|
||||||
}
|
}
|
||||||
@@ -40,6 +41,7 @@ class ToLowerFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test tolower with dict causes error`() {
|
fun `test tolower with dict causes error`() {
|
||||||
enterCommand("echo tolower({1: 2, 3: 4})")
|
enterCommand("echo tolower({1: 2, 3: 4})")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E731: Using a Dictionary as a String")
|
assertPluginErrorMessage("E731: Using a Dictionary as a String")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2025 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -33,6 +33,7 @@ class ToUpperFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test toupper with list causes error`() {
|
fun `test toupper with list causes error`() {
|
||||||
enterCommand("echo toupper([1, 2, 3])")
|
enterCommand("echo toupper([1, 2, 3])")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E730: Using a List as a String")
|
assertPluginErrorMessage("E730: Using a List as a String")
|
||||||
}
|
}
|
||||||
@@ -40,6 +41,7 @@ class ToUpperFunctionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test toupper with dict causes error`() {
|
fun `test toupper with dict causes error`() {
|
||||||
enterCommand("echo toupper({1: 2, 3: 4})")
|
enterCommand("echo toupper({1: 2, 3: 4})")
|
||||||
|
assertNoExOutput()
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E731: Using a Dictionary as a String")
|
assertPluginErrorMessage("E731: Using a Dictionary as a String")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -207,12 +207,7 @@ class FunctionDeclarationTest : VimTestCase() {
|
|||||||
typeText(commandToKeys("echo F1()"))
|
typeText(commandToKeys("echo F1()"))
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertPluginErrorMessage("E121: Undefined variable: x")
|
assertPluginErrorMessage("E121: Undefined variable: x")
|
||||||
assertExOutput(
|
assertExOutput("0")
|
||||||
"""
|
|
||||||
E121: Undefined variable: x
|
|
||||||
0
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
|
|
||||||
typeText(commandToKeys("delf! F1"))
|
typeText(commandToKeys("delf! F1"))
|
||||||
typeText(commandToKeys("delf! F2"))
|
typeText(commandToKeys("delf! F2"))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -154,12 +154,7 @@ class TryCatchTest : VimTestCase() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
assertPluginError(true)
|
assertPluginError(true)
|
||||||
assertExOutput(
|
assertExOutput("finally block")
|
||||||
"""
|
|
||||||
finally block
|
|
||||||
my exception
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -223,7 +223,7 @@ class SearchGroupTest : VimTestCase() {
|
|||||||
) {
|
) {
|
||||||
enterCommand("set nowrapscan")
|
enterCommand("set nowrapscan")
|
||||||
}
|
}
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
assertPluginErrorMessage("E385: Search hit BOTTOM without match for: one")
|
assertPluginErrorMessage("E385: Search hit BOTTOM without match for: one")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ class SearchGroupTest : VimTestCase() {
|
|||||||
three
|
three
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
assertPluginErrorMessage("E486: Pattern not found: banana")
|
assertPluginErrorMessage("E486: Pattern not found: banana")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ class SearchGroupTest : VimTestCase() {
|
|||||||
) {
|
) {
|
||||||
enterCommand("set nowrapscan")
|
enterCommand("set nowrapscan")
|
||||||
}
|
}
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
assertPluginErrorMessage("E384: Search hit TOP without match for: three")
|
assertPluginErrorMessage("E384: Search hit TOP without match for: three")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ class SearchGroupTest : VimTestCase() {
|
|||||||
three
|
three
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
assertPluginErrorMessage("E486: Pattern not found: banana")
|
assertPluginErrorMessage("E486: Pattern not found: banana")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,7 +615,7 @@ class SearchGroupTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
enterCommand("set nowrapscan")
|
enterCommand("set nowrapscan")
|
||||||
typeText("10", "/", searchCommand("one"))
|
typeText("10", "/", searchCommand("one"))
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
assertPluginErrorMessage("E385: Search hit BOTTOM without match for: one")
|
assertPluginErrorMessage("E385: Search hit BOTTOM without match for: one")
|
||||||
assertPosition(2, 0)
|
assertPosition(2, 0)
|
||||||
}
|
}
|
||||||
@@ -679,7 +679,7 @@ class SearchGroupTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
enterCommand("set nowrapscan")
|
enterCommand("set nowrapscan")
|
||||||
typeText("12", "?one<CR>")
|
typeText("12", "?one<CR>")
|
||||||
assertPluginError(true)
|
assertPluginError(false)
|
||||||
assertPluginErrorMessage("E384: Search hit TOP without match for: one")
|
assertPluginErrorMessage("E384: Search hit TOP without match for: one")
|
||||||
assertPosition(8, 0)
|
assertPosition(8, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -12,25 +12,7 @@ import com.maddyhome.idea.vim.helper.EngineMessageHelper
|
|||||||
import org.jetbrains.annotations.PropertyKey
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
|
||||||
interface VimMessages {
|
interface VimMessages {
|
||||||
/**
|
|
||||||
* Displays an informational message to the user.
|
|
||||||
* The message panel closes on any keystroke and passes the key through to the editor.
|
|
||||||
*/
|
|
||||||
fun showMessage(editor: VimEditor, message: String?)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays an error message to the user (typically in red).
|
|
||||||
* The message panel closes on any keystroke and passes the key through to the editor.
|
|
||||||
*/
|
|
||||||
fun showErrorMessage(editor: VimEditor, message: String?)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy method for displaying messages.
|
|
||||||
* @deprecated Use [showMessage] or [showErrorMessage] instead.
|
|
||||||
*/
|
|
||||||
@Deprecated("Use showMessage or showErrorMessage instead", ReplaceWith("showMessage(editor, message)"))
|
|
||||||
fun showStatusBarMessage(editor: VimEditor?, message: String?)
|
fun showStatusBarMessage(editor: VimEditor?, message: String?)
|
||||||
|
|
||||||
fun getStatusBarMessage(): String?
|
fun getStatusBarMessage(): String?
|
||||||
fun clearStatusBarMessage()
|
fun clearStatusBarMessage()
|
||||||
fun indicateError()
|
fun indicateError()
|
||||||
@@ -46,4 +28,13 @@ interface VimMessages {
|
|||||||
fun message(@PropertyKey(resourceBundle = EngineMessageHelper.BUNDLE) key: String, vararg params: Any): String
|
fun message(@PropertyKey(resourceBundle = EngineMessageHelper.BUNDLE) key: String, vararg params: Any): String
|
||||||
|
|
||||||
fun updateStatusBar(editor: VimEditor)
|
fun updateStatusBar(editor: VimEditor)
|
||||||
|
|
||||||
|
fun showMessage(editor: VimEditor, message: String) {
|
||||||
|
showStatusBarMessage(editor, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showErrorMessage(editor: VimEditor, message: String?) {
|
||||||
|
showStatusBarMessage(editor, message)
|
||||||
|
indicateError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -29,8 +29,7 @@ interface VimOutputPanel {
|
|||||||
* Note: The full text content is not updated in the display until [show] is invoked.
|
* Note: The full text content is not updated in the display until [show] is invoked.
|
||||||
*
|
*
|
||||||
* @param text The text to append.
|
* @param text The text to append.
|
||||||
* @param isNewLine Whether to start the appended text on a new line.
|
* @param isNewLine Whether to start the appended text on a new line. Defaults to true.
|
||||||
* @param messageType The type of message, used to determine text styling.
|
|
||||||
*/
|
*/
|
||||||
fun addText(text: String, isNewLine: Boolean = true, messageType: MessageType = MessageType.STANDARD)
|
fun addText(text: String, isNewLine: Boolean = true, messageType: MessageType = MessageType.STANDARD)
|
||||||
|
|
||||||
@@ -52,4 +51,4 @@ interface VimOutputPanel {
|
|||||||
|
|
||||||
fun setContent(text: String)
|
fun setContent(text: String)
|
||||||
fun clearText()
|
fun clearText()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -26,12 +26,8 @@ interface VimOutputPanelService {
|
|||||||
fun getCurrentOutputPanel(): VimOutputPanel?
|
fun getCurrentOutputPanel(): VimOutputPanel?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends text to the existing output panel or creates a new one with the given text and message type.
|
* Appends text to the existing output panel or creates a new one with the given text.
|
||||||
|
* Basic method that should be sufficient in most cases.
|
||||||
*/
|
*/
|
||||||
fun output(
|
fun output(editor: VimEditor, context: ExecutionContext, text: String)
|
||||||
editor: VimEditor,
|
}
|
||||||
context: ExecutionContext,
|
|
||||||
text: String,
|
|
||||||
messageType: MessageType = MessageType.STANDARD,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2026 The IdeaVim authors
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -13,9 +13,9 @@ abstract class VimOutputPanelServiceBase : VimOutputPanelService {
|
|||||||
return getCurrentOutputPanel() ?: create(editor, context)
|
return getCurrentOutputPanel() ?: create(editor, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun output(editor: VimEditor, context: ExecutionContext, text: String, messageType: MessageType) {
|
override fun output(editor: VimEditor, context: ExecutionContext, text: String) {
|
||||||
val panel = getOrCreate(editor, context)
|
val panel = getOrCreate(editor, context)
|
||||||
panel.addText(text, true, messageType)
|
panel.addText(text)
|
||||||
panel.show()
|
panel.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user