Skip to content

Commit

Permalink
Add 'colorcolumn' option to show visual guides
Browse files Browse the repository at this point in the history
IntelliJ ties the hard wrap right margin guide with the other visual guides, and it's not possible to show one without the other. In Vim, you can show the hard wrap margin by adding "+0" to 'colorcolumn', so in IdeaVim, we automatically add this.
  • Loading branch information
citizenmatt committed Jan 11, 2024
1 parent ebf2a97 commit c896205
Show file tree
Hide file tree
Showing 9 changed files with 552 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
public class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) {
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
public val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
public var list: Boolean by optionProperty(IjOptions.list)
public var textwidth: Int by optionProperty(IjOptions.textwidth)
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/maddyhome/idea/vim/group/IjOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.group

import com.intellij.openapi.application.ApplicationNamesInfo
import com.maddyhome.idea.vim.api.Options
import com.maddyhome.idea.vim.ex.exExceptionMessage
import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL
Expand Down Expand Up @@ -38,6 +39,21 @@ public object IjOptions {

// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
public val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
public val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
override fun checkIfValueValid(value: VimDataType, token: String) {
super.checkIfValueValid(value, token)
if (value != VimString.EMPTY) {
// Each element in the comma separated string list needs to be a number. No spaces. Vim supports numbers
// beginning "+" or "-" to draw a highlight column relative to the 'textwidth' value. We don't fully support
// that, but we do automtaically add "+0" because IntelliJ always displays the right margin
split((value as VimString).asString()).forEach {
if (!it.matches(Regex("[+-]?[0-9]+"))) {
throw exExceptionMessage("E474", token)
}
}
}
}
})
public val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
public val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
public val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
Expand Down
89 changes: 89 additions & 0 deletions src/main/java/com/maddyhome/idea/vim/group/OptionGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.OptionAccessScope
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.ToggleOption
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import com.maddyhome.idea.vim.vimscript.model.datatypes.asVimInt

internal interface IjVimOptionGroup: VimOptionGroup {
Expand All @@ -46,6 +48,7 @@ internal interface IjVimOptionGroup: VimOptionGroup {
internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
init {
addOptionValueOverride(IjOptions.breakindent, BreakIndentOptionValueProvider(IjOptions.breakindent))
addOptionValueOverride(IjOptions.colorcolumn, ColorColumnOptionValueProvider(IjOptions.colorcolumn))
addOptionValueOverride(IjOptions.cursorline, CursorLineOptionValueProvider(IjOptions.cursorline))
addOptionValueOverride(IjOptions.list, ListOptionValueProvider(IjOptions.list))
addOptionValueOverride(IjOptions.textwidth, TextWidthOptionValueProvider(IjOptions.textwidth))
Expand Down Expand Up @@ -136,6 +139,92 @@ private class BreakIndentOptionValueProvider(breakIndentOption: ToggleOption)
}
}

private class ColorColumnOptionValueProvider(private val colorColumnOption: StringListOption) :
IdeOptionValueOverride<VimString>(colorColumnOption) {
override fun getIdeCurrentValue(editor: VimEditor): VimString {
// If isRightMarginShown is disabled, then we don't show any visual guides, including the right margin
if (!editor.ij.settings.isRightMarginShown) {
return VimString.EMPTY
}

val softMargins = editor.ij.settings.softMargins
return VimString(buildString {
softMargins.joinTo(this, ",")

// IntelliJ treats right margin and visual guides as the same - if we're showing either, we're showing both.
// Vim supports the "+0" syntax to show a highlight column relative to the 'textwidth' value. The user can set
// the value to an empty string to remove this, and disable the right margin.
// IntelliJ behaves slightly differently to Vim here - "+0" in Vim will only show the column if 'textwidth' is
// set, while IntelliJ will show the current right margin even if wrap at margin is false.
if (this.isNotEmpty()) append(",")
append("+0")
})
}

override fun getIdeDefaultValue(editor: VimEditor): VimString {
if (!EditorSettingsExternalizable.getInstance().isRightMarginShown) {
return VimString.EMPTY
}

val ijEditor = editor.ij
val language = ijEditor.project?.let { TextEditorImpl.getDocumentLanguage(ijEditor) }
val softMargins = CodeStyle.getSettings(ijEditor).getSoftMargins(language)
return VimString(buildString {
softMargins.joinTo(this, ",")

// Add the default "+0" to mimic Vim showing the 'textwidth' column. See above.
if (this.isNotEmpty()) append(",")
append("+0")
})
}

override fun setIdeValue(editor: VimEditor, value: VimString) {
// Given an empty string, hide the margin.
if (value == VimString.EMPTY) {
editor.ij.settings.isRightMarginShown = false
}
else {
editor.ij.settings.isRightMarginShown = true

val softMargins = mutableListOf<Int>()
colorColumnOption.split(value.value).forEach {
if (it.startsWith("+") || it.startsWith("-")) {
// TODO: Support ±1, ±2, ±n, etc. But this is difficult
// This would need a listener for the right margin IntelliJ value, and would still add a visual guide at +0
// We'd also need some mechanism for saving the relative offsets. The override getters would return real
// column values, while the stored Vim option will be relative
// We could perhaps add a property change listener from editor settings state?
// (editor.ij as EditorImpl).state.addPropertyChangeListener(...)
// (editor.ij.settings as SettingsImpl).getState().addPropertyChangeListener(...)
}
else {
it.toIntOrNull()?.let(softMargins::add)
}
}
editor.ij.settings.setSoftMargins(softMargins)
}
}

override fun resetDefaultValue(editor: VimEditor) {
// Reset the current settings back to default by setting both the flag and the visual guides
val ijEditor = editor.ij
val language = ijEditor.project?.let { TextEditorImpl.getDocumentLanguage(ijEditor) }

// Remember to only update if the value has changed! We don't want to force the global-local values to local only
if (ijEditor.settings.isRightMarginShown != EditorSettingsExternalizable.getInstance().isRightMarginShown) {
ijEditor.settings.isRightMarginShown = EditorSettingsExternalizable.getInstance().isRightMarginShown
}

val codeStyle = CodeStyle.getSettings(ijEditor)
val globalSoftMargins = codeStyle.getSoftMargins(language)
val localSoftMargins = ijEditor.settings.softMargins

if (globalSoftMargins.count() != localSoftMargins.count() || !localSoftMargins.containsAll(globalSoftMargins)) {
ijEditor.settings.setSoftMargins(codeStyle.getSoftMargins(language))
}
}
}

private class CursorLineOptionValueProvider(cursorLineOption: ToggleOption) :
IdeOptionValueOverride<VimInt>(cursorLineOption) {
override fun getIdeCurrentValue(editor: VimEditor) = editor.ij.settings.isCaretRowShown.asVimInt()
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/dictionaries/ideavim.dic
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ ideavimrc
maddyhome

breakindent
colorcolumn
cursorline
gdefault
guicursor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SetCommandTest : VimTestCase() {
configureByText("\n")

// Some options reflect the state of IntelliJ values when not explicitly set. Make sure we've got consistent values
fixture.editor.settings.isRightMarginShown = false // Otherwise we get `colorcolumn=+0`
fixture.editor.settings.isCaretRowShown = IjOptions.cursorline.defaultValue.asBoolean()
fixture.editor.settings.isUseCustomSoftWrapIndent = IjOptions.breakindent.defaultValue.asBoolean()
fixture.editor.settings.isWhitespacesShown = IjOptions.list.defaultValue.asBoolean()
Expand Down Expand Up @@ -173,20 +174,21 @@ class SetCommandTest : VimTestCase() {
assertCommandOutput("set all",
"""
|--- Options ---
|noargtextobj ideastrictmode scroll=0 notextobj-entire
|nobreakindent noideatracetime scrolljump=1 notextobj-indent
| closenotebooks ideawrite=all scrolloff=0 textwidth=0
|nocommentary noignorecase selectmode= timeout
|nocursorline noincsearch shellcmdflag=-x timeoutlen=1000
|nodigraph nolist shellxescape=@ notrackactionids
|noexchange nomatchit shellxquote={ undolevels=1000
|nogdefault maxmapdepth=20 showcmd unifyjumps
|nohighlightedyank more showmode virtualedit=
| history=50 nomultiple-cursors sidescroll=0 novisualbell
|nohlsearch noNERDTree sidescrolloff=0 visualdelay=100
|noideaglobalmode nrformats=hex nosmartcase whichwrap=b,s
|noideajoin nonumber startofline wrap
| ideamarks norelativenumber nosurround wrapscan
|noargtextobj ideastrictmode scrolljump=1 textwidth=0
|nobreakindent noideatracetime scrolloff=0 timeout
| closenotebooks ideawrite=all selectmode= timeoutlen=1000
| colorcolumn= noignorecase shellcmdflag=-x notrackactionids
|nocommentary noincsearch shellxescape=@ undolevels=1000
|nocursorline nolist shellxquote={ unifyjumps
|nodigraph nomatchit showcmd virtualedit=
|noexchange maxmapdepth=20 showmode novisualbell
|nogdefault more sidescroll=0 visualdelay=100
|nohighlightedyank nomultiple-cursors sidescrolloff=0 whichwrap=b,s
| history=50 noNERDTree nosmartcase wrap
|nohlsearch nrformats=hex startofline wrapscan
|noideaglobalmode nonumber nosurround
|noideajoin norelativenumber notextobj-entire
| ideamarks scroll=0 notextobj-indent
| clipboard=ideaput,autoselect,exclude:cons\|linux
| excommandannotation
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
Expand Down Expand Up @@ -238,6 +240,7 @@ class SetCommandTest : VimTestCase() {
|nobreakindent
| clipboard=ideaput,autoselect,exclude:cons\|linux
| closenotebooks
| colorcolumn=
|nocommentary
|nocursorline
|nodigraph
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class SetglobalCommandTest : VimTestCase() {
configureByText("\n")

// Some options reflect the state of IntelliJ values when not explicitly set. Make sure we've got consistent values
fixture.editor.settings.isRightMarginShown = false // Otherwise we get `colorcolumn=+0`
fixture.editor.settings.isCaretRowShown = IjOptions.cursorline.defaultValue.asBoolean()
fixture.editor.settings.isUseCustomSoftWrapIndent = IjOptions.breakindent.defaultValue.asBoolean()
fixture.editor.settings.isWhitespacesShown = IjOptions.list.defaultValue.asBoolean()
Expand Down Expand Up @@ -359,20 +360,21 @@ class SetglobalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues()
assertCommandOutput("setglobal all", """
|--- Global option values ---
|noargtextobj ideastrictmode scroll=0 notextobj-entire
|nobreakindent noideatracetime scrolljump=1 notextobj-indent
| closenotebooks ideawrite=all scrolloff=0 textwidth=0
|nocommentary noignorecase selectmode= timeout
|nocursorline noincsearch shellcmdflag=-x timeoutlen=1000
|nodigraph nolist shellxescape=@ notrackactionids
|noexchange nomatchit shellxquote={ undolevels=1000
|nogdefault maxmapdepth=20 showcmd unifyjumps
|nohighlightedyank more showmode virtualedit=
| history=50 nomultiple-cursors sidescroll=0 novisualbell
|nohlsearch noNERDTree sidescrolloff=0 visualdelay=100
|noideaglobalmode nrformats=hex nosmartcase whichwrap=b,s
|noideajoin nonumber startofline wrap
| ideamarks norelativenumber nosurround wrapscan
|noargtextobj ideastrictmode scrolljump=1 textwidth=0
|nobreakindent noideatracetime scrolloff=0 timeout
| closenotebooks ideawrite=all selectmode= timeoutlen=1000
| colorcolumn= noignorecase shellcmdflag=-x notrackactionids
|nocommentary noincsearch shellxescape=@ undolevels=1000
|nocursorline nolist shellxquote={ unifyjumps
|nodigraph nomatchit showcmd virtualedit=
|noexchange maxmapdepth=20 showmode novisualbell
|nogdefault more sidescroll=0 visualdelay=100
|nohighlightedyank nomultiple-cursors sidescrolloff=0 whichwrap=b,s
| history=50 noNERDTree nosmartcase wrap
|nohlsearch nrformats=hex startofline wrapscan
|noideaglobalmode nonumber nosurround
|noideajoin norelativenumber notextobj-entire
| ideamarks scroll=0 notextobj-indent
| clipboard=ideaput,autoselect,exclude:cons\|linux
| excommandannotation
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
Expand Down Expand Up @@ -420,6 +422,7 @@ class SetglobalCommandTest : VimTestCase() {
|nobreakindent
| clipboard=ideaput,autoselect,exclude:cons\|linux
| closenotebooks
| colorcolumn=
|nocommentary
|nocursorline
|nodigraph
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SetlocalCommandTest : VimTestCase() {
configureByText("\n")

// Some options reflect the state of IntelliJ values when not explicitly set. Make sure we've got consistent values
fixture.editor.settings.isRightMarginShown = false // Otherwise we get `colorcolumn=+0`
fixture.editor.settings.isCaretRowShown = IjOptions.cursorline.defaultValue.asBoolean()
fixture.editor.settings.isUseCustomSoftWrapIndent = IjOptions.breakindent.defaultValue.asBoolean()
fixture.editor.settings.isWhitespacesShown = IjOptions.list.defaultValue.asBoolean()
Expand Down Expand Up @@ -391,20 +392,21 @@ class SetlocalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues()
assertCommandOutput("setlocal all", """
|--- Local option values ---
|noargtextobj idearefactormode= norelativenumber nosurround
|nobreakindent ideastrictmode scroll=0 notextobj-entire
| closenotebooks noideatracetime scrolljump=1 notextobj-indent
|nocommentary ideawrite=all scrolloff=-1 textwidth=0
|nocursorline noignorecase selectmode= timeout
|nodigraph noincsearch shellcmdflag=-x timeoutlen=1000
|noexchange nolist shellxescape=@ notrackactionids
|nogdefault nomatchit shellxquote={ unifyjumps
|nohighlightedyank maxmapdepth=20 showcmd virtualedit=
| history=50 more showmode novisualbell
|nohlsearch nomultiple-cursors sidescroll=0 visualdelay=100
|noideaglobalmode noNERDTree sidescrolloff=-1 whichwrap=b,s
|--ideajoin nrformats=hex nosmartcase wrap
| ideamarks nonumber startofline wrapscan
|noargtextobj idearefactormode= scroll=0 notextobj-indent
|nobreakindent ideastrictmode scrolljump=1 textwidth=0
| closenotebooks noideatracetime scrolloff=-1 timeout
| colorcolumn= ideawrite=all selectmode= timeoutlen=1000
|nocommentary noignorecase shellcmdflag=-x notrackactionids
|nocursorline noincsearch shellxescape=@ unifyjumps
|nodigraph nolist shellxquote={ virtualedit=
|noexchange nomatchit showcmd novisualbell
|nogdefault maxmapdepth=20 showmode visualdelay=100
|nohighlightedyank more sidescroll=0 whichwrap=b,s
| history=50 nomultiple-cursors sidescrolloff=-1 wrap
|nohlsearch noNERDTree nosmartcase wrapscan
|noideaglobalmode nrformats=hex startofline
|--ideajoin nonumber nosurround
| ideamarks norelativenumber notextobj-entire
| clipboard=ideaput,autoselect,exclude:cons\|linux
| excommandannotation
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
Expand Down Expand Up @@ -458,6 +460,7 @@ class SetlocalCommandTest : VimTestCase() {
|nobreakindent
| clipboard=ideaput,autoselect,exclude:cons\|linux
| closenotebooks
| colorcolumn=
|nocommentary
|nocursorline
|nodigraph
Expand Down
Loading

0 comments on commit c896205

Please sign in to comment.