Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

guicursor! #337

Merged
merged 36 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
646a89d
Refactor showMode update
citizenmatt Jun 4, 2021
2be0f5c
Reset caret visual position after mode changes
citizenmatt Jun 4, 2021
b50281f
Extract caret shape methods to helper
citizenmatt Jun 4, 2021
6c4bd9c
Refactor caret shape functions
citizenmatt Jun 4, 2021
e859b1c
Remove resetCaret
citizenmatt Jun 4, 2021
9c71b44
Update caret visual attributes when mode changes
citizenmatt Jun 5, 2021
55dedb4
Remove resetShape method
citizenmatt Jun 10, 2021
dfbec1f
Rename "bound" options to "bounded"
citizenmatt Jun 10, 2021
287ba70
Simplify BoundedListOption
citizenmatt Jun 10, 2021
e93a619
Make ListOption generic + create StringListOption
citizenmatt Jun 10, 2021
9f46e19
Fix typo
citizenmatt Jun 10, 2021
43620c2
Add guicursor option
citizenmatt Jun 10, 2021
9cf0a1a
Report errors while parsing guicursor option
citizenmatt Jun 11, 2021
8a55199
Add guicursor to dictionary
citizenmatt Jun 11, 2021
ccd792b
Provide hook for resetting cached values
citizenmatt Jun 28, 2021
d19c776
Minor refactor
citizenmatt Jun 28, 2021
ad19dc0
Use guicursor options to draw caret
citizenmatt Jun 28, 2021
b4d40fa
Rename assert method
citizenmatt Jun 28, 2021
a6087dd
Use replace mode caret for change character action
citizenmatt Jun 28, 2021
2f73dac
Force the caret visible when updating attributes
citizenmatt Jun 28, 2021
f051231
Use guicursor instead of editor cursor settings
citizenmatt Jun 28, 2021
64be751
Use guicursor options to draw ex caret
citizenmatt Jun 29, 2021
1caf380
Improve caret painting in command line
citizenmatt Jun 29, 2021
fe7dc49
Make ExShortcutKeyAction dumb aware
citizenmatt Jun 30, 2021
0288a0f
Use insert caret for select mode
citizenmatt Jun 30, 2021
0d840b2
Merge branch 'master' into feature/guicursor
citizenmatt Jun 30, 2021
d6a99d4
Update to latest EAP
citizenmatt Jun 30, 2021
91585e1
Run linters
citizenmatt Jun 30, 2021
d63e3da
Merge branch 'master' into feature/guicursor
citizenmatt Jul 28, 2021
70a45f3
Treat use block caret option as caret override
citizenmatt Jul 29, 2021
6ab5380
Add tests for caret attributes in nested modes
citizenmatt Aug 10, 2021
ae21727
Merge branch 'master' into feature/guicursor
citizenmatt Sep 27, 2021
74b0e3b
Merge branch 'master' into feature/guicursor
AlexPl292 Sep 29, 2021
c268407
Merge branch 'master' into feature/guicursor
AlexPl292 Sep 29, 2021
3fae2fd
isBlockCursor method doesn't work for UI tests at the moment
AlexPl292 Sep 29, 2021
ee74367
Merge branch 'master' into feature/guicursor
AlexPl292 Oct 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/dictionaries/ideavim.dic
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ ideavimrc


gdefault
guicursor
hlsearch
ideamarks
ignorecase
Expand Down
4 changes: 4 additions & 0 deletions resources/messages/IdeaVimBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ e_patnotf2=Pattern not found: {0}
unkopt=Unknown option: {0}
e_invarg=Invalid argument: {0}
E475=E475: Invalid argument: {0}
E545=E545: Missing colon: {0}
E546=E546: Illegal mode: {0}
E548=E548: Digit expected: {0}
E549=E549: Illegal percentage: {0}
E774=E774: 'operatorfunc' is empty

action.VimPluginToggle.text=Vim Emulator
Expand Down
24 changes: 17 additions & 7 deletions src/com/maddyhome/idea/vim/KeyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Ref;
import com.maddyhome.idea.vim.action.change.VimRepeater;
import com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction;
import com.maddyhome.idea.vim.action.change.change.ChangeVisualCharacterAction;
import com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction;
import com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction;
import com.maddyhome.idea.vim.action.macro.ToggleRecordingAction;
import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.group.ChangeGroup;
import com.maddyhome.idea.vim.group.RegisterGroup;
import com.maddyhome.idea.vim.group.visual.VisualGroupKt;
import com.maddyhome.idea.vim.handler.ActionBeanClass;
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
import com.maddyhome.idea.vim.helper.*;
Expand Down Expand Up @@ -301,6 +301,7 @@ else if (!handleDigraph(editor, key, context, editorState)) {
else if (commandBuilder.isBad()) {
editorState.resetOpPending();
editorState.resetRegisterPending();
editorState.resetReplaceCharacter();
VimPlugin.indicateError();
reset(editor);
}
Expand Down Expand Up @@ -337,7 +338,11 @@ public static <T> boolean isPrefix(@NotNull List<T> list1, @NotNull List<T> list
}

private void handleEditorReset(@NotNull Editor editor, @NotNull KeyStroke key, final @NotNull DataContext context, @NotNull CommandState editorState) {
if (editorState.getCommandBuilder().isAtDefaultState()) {
final CommandBuilder commandBuilder = editorState.getCommandBuilder();
if (commandBuilder.isAwaitingCharOrDigraphArgument()) {
editorState.resetReplaceCharacter();
}
if (commandBuilder.isAtDefaultState()) {
RegisterGroup register = VimPlugin.getRegister();
if (register.getCurrentRegister() == register.getDefaultRegister()) {
boolean indicateError = true;
Expand All @@ -357,7 +362,6 @@ private void handleEditorReset(@NotNull Editor editor, @NotNull KeyStroke key, f
}
}
reset(editor);
ChangeGroup.resetCaret(editor, false);
}

private boolean handleKeyMapping(final @NotNull Editor editor,
Expand Down Expand Up @@ -609,6 +613,8 @@ private void handleCharArgument(@NotNull KeyStroke key, char chKey, @NotNull Com
// Oops - this isn't a valid character argument
commandBuilder.setCommandState(CurrentCommandState.BAD_COMMAND);
}

commandState.resetReplaceCharacter();
}

private boolean handleDigraph(@NotNull Editor editor,
Expand Down Expand Up @@ -811,7 +817,8 @@ private void startWaitingForArgument(Editor editor,
if (action instanceof InsertCompletedDigraphAction) {
editorState.startDigraphSequence();
setPromptCharacterEx('?');
} else if (action instanceof InsertCompletedLiteralAction) {
}
else if (action instanceof InsertCompletedLiteralAction) {
editorState.startLiteralSequence();
setPromptCharacterEx('^');
}
Expand All @@ -824,6 +831,11 @@ private void startWaitingForArgument(Editor editor,
editorState.pushModes(CommandState.Mode.CMD_LINE, CommandState.SubMode.NONE);
break;
}

// Another special case. Force a mode change to update the caret shape
if (action instanceof ChangeCharacterAction || action instanceof ChangeVisualCharacterAction) {
editorState.pushModes(editorState.getMode(), CommandState.SubMode.REPLACE_CHARACTER);
}
}

private boolean checkArgumentCompatibility(@Nullable Argument.Type expectedArgumentType, @NotNull EditorActionHandlerBase action) {
Expand Down Expand Up @@ -883,7 +895,6 @@ public void fullReset(@NotNull Editor editor) {
if (registerGroup != null) {
registerGroup.resetRegister();
}
VisualGroupKt.updateCaretState(editor);
editor.getSelectionModel().removeSelection();
}

Expand Down Expand Up @@ -978,7 +989,6 @@ public void run() {
if (editorState.getSubMode() == CommandState.SubMode.SINGLE_COMMAND &&
(!cmd.getFlags().contains(CommandFlags.FLAG_EXPECT_MORE))) {
editorState.popModes();
VisualGroupKt.resetShape(CommandStateHelper.getMode(editor), editor);
}

if (editorState.getCommandBuilder().isDone()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.group.visual.updateCaretState
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.commandState
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
Expand Down Expand Up @@ -60,7 +59,6 @@ class SelectToggleVisualMode : VimActionHandler.SingleExecution() {
}
}
}
updateCaretState(editor)
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.option.ListOption
import com.maddyhome.idea.vim.option.OptionsManager.selectmode
import com.maddyhome.idea.vim.option.StringListOption

class VisualToggleBlockModeAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY

override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
val listOption: ListOption = selectmode
val listOption: StringListOption = selectmode
return if (listOption.contains("cmd")) {
VimPlugin.getVisualMotion().enterSelectMode(editor, CommandState.SubMode.VISUAL_BLOCK)
} else VimPlugin.getVisualMotion()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.option.ListOption
import com.maddyhome.idea.vim.option.OptionsManager.selectmode
import com.maddyhome.idea.vim.option.StringListOption

class VisualToggleCharacterModeAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY

override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
val listOption: ListOption = selectmode
val listOption: StringListOption = selectmode
return if (listOption.contains("cmd")) {
VimPlugin.getVisualMotion().enterSelectMode(editor, CommandState.SubMode.VISUAL_CHARACTER)
} else VimPlugin.getVisualMotion()
Expand Down
50 changes: 35 additions & 15 deletions src/com/maddyhome/idea/vim/command/CommandState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import com.maddyhome.idea.vim.helper.DigraphSequence
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
import com.maddyhome.idea.vim.helper.vimCommandState
import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.option.OptionsManager.showmode
Expand All @@ -35,17 +37,17 @@ import java.util.*
import javax.swing.KeyStroke

/**
* Used to maintain state while entering a Vim command (operator, motion, text object, etc.)
* Used to maintain state before and while entering a Vim command (operator, motion, text object, etc.)
*/
class CommandState private constructor() {
class CommandState private constructor(private val editor: Editor) {
val commandBuilder = CommandBuilder(getKeyRootNode(MappingMode.NORMAL))
private val modeStates = Stack<ModeState>()
val mappingState = MappingState()
private val digraphSequence = DigraphSequence()
var isRecording = false
set(value) {
field = value
updateStatus()
doShowMode()
}
var isDotRepeatInProgress = false

Expand Down Expand Up @@ -78,17 +80,26 @@ class CommandState private constructor() {

fun pushModes(mode: Mode, submode: SubMode) {
val newModeState = ModeState(mode, submode)

logger.debug("Push new mode state: ${newModeState.toSimpleString()}")
logger.debug { "Stack of mode states before push: ${toSimpleString()}" }

val previousMode = currentModeState()
modeStates.push(newModeState)
setMappingMode()
updateStatus()

if (previousMode != newModeState) {
onModeChanged()
}
}

fun popModes() {
val popped = modeStates.pop()
setMappingMode()
updateStatus()
if (popped != currentModeState()) {
onModeChanged()
}

logger.debug("Popped mode state: ${popped.toSimpleString()}")
logger.debug { "Stack of mode states after pop: ${toSimpleString()}" }
}
Expand All @@ -99,6 +110,12 @@ class CommandState private constructor() {
}
}

fun resetReplaceCharacter() {
if (subMode == SubMode.REPLACE_CHARACTER) {
popModes()
}
}

fun resetRegisterPending() {
if (subMode == SubMode.REGISTER_PENDING) {
popModes()
Expand All @@ -107,13 +124,18 @@ class CommandState private constructor() {

private fun resetModes() {
modeStates.clear()
onModeChanged()
setMappingMode()
}

private fun onModeChanged() {
AlexPl292 marked this conversation as resolved.
Show resolved Hide resolved
editor.updateCaretsVisualAttributes()
editor.updateCaretsVisualPosition()
doShowMode()
}

private fun setMappingMode() {
val modeState = currentModeState()
val newMappingMode = if (modeState.mode == Mode.OP_PENDING) MappingMode.OP_PENDING else modeToMappingMode(mode)
mappingState.mappingMode = newMappingMode
mappingState.mappingMode = modeToMappingMode(mode)
}

@Contract(pure = true)
Expand All @@ -124,7 +146,7 @@ class CommandState private constructor() {
Mode.VISUAL -> MappingMode.VISUAL
Mode.SELECT -> MappingMode.SELECT
Mode.CMD_LINE -> MappingMode.CMD_LINE
else -> error("Unexpected mode: $mode")
Mode.OP_PENDING -> MappingMode.OP_PENDING
}
}

Expand All @@ -137,7 +159,6 @@ class CommandState private constructor() {
val modeState = currentModeState()
popModes()
pushModes(modeState.mode, submode)
updateStatus()
}

fun startDigraphSequence() {
Expand Down Expand Up @@ -183,7 +204,6 @@ class CommandState private constructor() {
resetModes()
commandBuilder.resetInProgressCommandPart(getKeyRootNode(mappingState.mappingMode))
digraphSequence.reset()
updateStatus()
}

fun toSimpleString(): String = modeStates.joinToString { it.toSimpleString() }
Expand Down Expand Up @@ -262,7 +282,7 @@ class CommandState private constructor() {
return if (modeStates.size > 0) modeStates.peek() else defaultModeState
}

private fun updateStatus() {
private fun doShowMode() {
val msg = StringBuilder()
if (showmode.isSet) {
msg.append(getStatusString(modeStates.size - 1))
Expand Down Expand Up @@ -319,10 +339,10 @@ class CommandState private constructor() {
}

enum class SubMode {
NONE, SINGLE_COMMAND, REGISTER_PENDING, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
NONE, SINGLE_COMMAND, REGISTER_PENDING, REPLACE_CHARACTER, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we add this new mode to catch the moment when the r is pressed, but replaced character is not yet typed by user?

I just don't understand why REPLACE mode is not enough.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's it.

I introduced REPLACE_CHARACTER because I don't think this is a good fit for the existing REPLACE mode. It's tracking the state of an incomplete command, rather than actually switching into REPLACE mode. It doesn't feel right to do a full mode switch in the middle of command entry - we're not actually in replace mode, we're waiting for an argument to a command that will do a replace. We want to look like we're in replace mode, but not actually be in it.

And pragmatically, if we used REPLACE, we'd still need another piece of state to know when to pop the REPLACE mode if hitting <Esc> or entering an incorrect digraph. E.g. handleDigraph doesn't currently pop modes, so if we're entering a digraph, we don't know if we're entering it in actual replace mode, or as the argument for an incomplete action. The sub mode gives us everything we need, and tells us we're still in COMMAND or VISUAL mode (which we are, waiting for a command to complete), and gives us something we can update the caret with.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thank you for explanation. I have some thoughts about removing subMode at all because it doesn't exist in vim, but it might be a future refactoring. Let's use our current structure.

}

private class ModeState(val mode: Mode, val subMode: SubMode) {
private data class ModeState(val mode: Mode, val subMode: SubMode) {
fun toSimpleString(): String = "$mode:$subMode"
}

Expand All @@ -334,7 +354,7 @@ class CommandState private constructor() {
fun getInstance(editor: Editor): CommandState {
var res = editor.vimCommandState
if (res == null) {
res = CommandState()
res = CommandState(editor)
editor.vimCommandState = res
}
return res
Expand Down
13 changes: 12 additions & 1 deletion src/com/maddyhome/idea/vim/ex/ExExceptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,18 @@
*/
package com.maddyhome.idea.vim.ex

open class ExException(s: String? = null) : Exception(s)
import com.maddyhome.idea.vim.helper.MessageHelper
import org.jetbrains.annotations.PropertyKey

open class ExException(s: String? = null) : Exception(s) {
var code: String? = null
private set

companion object {
fun message(@PropertyKey(resourceBundle = MessageHelper.BUNDLE) code: String, vararg params: Any) =
ExException(MessageHelper.message(code, *params)).apply { this.code = code }
}
}

class InvalidCommandException(message: String, cmd: String?) : ExException(message + if (cmd != null) " | $cmd" else "")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.userData
import com.maddyhome.idea.vim.option.OptionsManager
import org.jetbrains.annotations.NonNls
Expand Down Expand Up @@ -173,6 +174,7 @@ class VimMultipleCursorsExtension : VimExtension {
if (newPositions.size > 0) {
editor.exitVisualMode()
newPositions.forEach { editor.caretModel.addCaret(it, true) ?: return }
editor.updateCaretsVisualAttributes()
return
}

Expand Down Expand Up @@ -216,6 +218,7 @@ class VimMultipleCursorsExtension : VimExtension {
}

val caret = editor.caretModel.addCaret(editor.offsetToVisualPosition(nextOffset), true) ?: return
editor.updateCaretsVisualAttributes()
editor.vimMultipleCursorsLastSelection = selectText(caret, pattern, nextOffset)
} else {
VimPlugin.showMessage(MessageHelper.message("message.no.more.matches"))
Expand Down Expand Up @@ -254,6 +257,7 @@ class VimMultipleCursorsExtension : VimExtension {
selectText(caret, text, match.startOffset)
}
}
editor.updateCaretsVisualAttributes()
}
}

Expand Down
Loading