Skip to content

Commit

Permalink
Add 'textwidth' option
Browse files Browse the repository at this point in the history
Also supports overriding local-to-buffer options with IDE values, ensuring that changes to the option/IDE value are applied to all editors for the buffer.

Fixes VIM-1310
  • Loading branch information
citizenmatt committed Jan 11, 2024
1 parent 8df233e commit ebf2a97
Show file tree
Hide file tree
Showing 9 changed files with 581 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOpt
public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
public var list: Boolean by optionProperty(IjOptions.list)
public var textwidth: Int by optionProperty(IjOptions.textwidth)
public var wrap: Boolean by optionProperty(IjOptions.wrap)

// IntelliJ specific options
Expand Down
3 changes: 3 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,9 +10,11 @@ package com.maddyhome.idea.vim.group

import com.intellij.openapi.application.ApplicationNamesInfo
import com.maddyhome.idea.vim.api.Options
import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_BUFFER
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_WINDOW
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
Expand All @@ -38,6 +40,7 @@ public object IjOptions {
public val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
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))
public val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true))

// IntelliJ specific functionality - custom options
Expand Down
98 changes: 90 additions & 8 deletions src/main/java/com/maddyhome/idea/vim/group/OptionGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

package com.maddyhome.idea.vim.group

import com.intellij.application.options.CodeStyle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl
import com.intellij.openapi.project.ProjectManager
import com.intellij.util.PatternUtil
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.IdeOptionValueOverride
Expand All @@ -22,7 +25,9 @@ import com.maddyhome.idea.vim.api.VimOptionGroupBase
import com.maddyhome.idea.vim.api.injector
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.ToggleOption
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.asVimInt

Expand All @@ -40,10 +45,11 @@ internal interface IjVimOptionGroup: VimOptionGroup {

internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
init {
addOptionValueOverride(IjOptions.breakindent, BreakIndentOptionValueProvider())
addOptionValueOverride(IjOptions.cursorline, CursorLineOptionValueProvider())
addOptionValueOverride(IjOptions.list, ListOptionValueProvider())
addOptionValueOverride(IjOptions.wrap, WrapOptionValueProvider())
addOptionValueOverride(IjOptions.breakindent, BreakIndentOptionValueProvider(IjOptions.breakindent))
addOptionValueOverride(IjOptions.cursorline, CursorLineOptionValueProvider(IjOptions.cursorline))
addOptionValueOverride(IjOptions.list, ListOptionValueProvider(IjOptions.list))
addOptionValueOverride(IjOptions.textwidth, TextWidthOptionValueProvider(IjOptions.textwidth))
addOptionValueOverride(IjOptions.wrap, WrapOptionValueProvider(IjOptions.wrap))
}

override fun initialiseOptions() {
Expand Down Expand Up @@ -118,7 +124,8 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
*/

// TODO: We could also implement 'breakindentopt', but only the shift:{n} component would be supportable
private class BreakIndentOptionValueProvider : IdeOptionValueOverride<VimInt>() {
private class BreakIndentOptionValueProvider(breakIndentOption: ToggleOption)
: IdeOptionValueOverride<VimInt>(breakIndentOption) {
override fun getIdeCurrentValue(editor: VimEditor) = editor.ij.settings.isUseCustomSoftWrapIndent.asVimInt()

override fun getIdeDefaultValue(editor: VimEditor) =
Expand All @@ -129,7 +136,8 @@ private class BreakIndentOptionValueProvider : IdeOptionValueOverride<VimInt>()
}
}

private class CursorLineOptionValueProvider : IdeOptionValueOverride<VimInt>() {
private class CursorLineOptionValueProvider(cursorLineOption: ToggleOption) :
IdeOptionValueOverride<VimInt>(cursorLineOption) {
override fun getIdeCurrentValue(editor: VimEditor) = editor.ij.settings.isCaretRowShown.asVimInt()

override fun getIdeDefaultValue(editor: VimEditor) =
Expand All @@ -140,7 +148,7 @@ private class CursorLineOptionValueProvider : IdeOptionValueOverride<VimInt>() {
}
}

private class ListOptionValueProvider : IdeOptionValueOverride<VimInt>() {
private class ListOptionValueProvider(listOption: ToggleOption) : IdeOptionValueOverride<VimInt>(listOption) {
override fun getIdeCurrentValue(editor: VimEditor) = editor.ij.settings.isWhitespacesShown.asVimInt()
override fun getIdeDefaultValue(editor: VimEditor) =
EditorSettingsExternalizable.getInstance().isWhitespacesShown.asVimInt()
Expand All @@ -150,7 +158,81 @@ private class ListOptionValueProvider : IdeOptionValueOverride<VimInt>() {
}
}

private class WrapOptionValueProvider : IdeOptionValueOverride<VimInt>() {
/**
* Map the 'textwidth' Vim option to the IntelliJ hard wrap settings
*
* Note that this option is local-to-buffer, while the IntelliJ settings are either per-language, or local editor
* (window) overrides. The [IdeOptionValueOverride] base class will handle this by calling [setIdeValue] for all open
* editors for the changed buffer.
*/
private class TextWidthOptionValueProvider(textWidthOption: NumberOption)
: IdeOptionValueOverride<VimInt>(textWidthOption) {
override fun getIdeCurrentValue(editor: VimEditor): VimInt {
// This requires a non-null project due to Kotlin's type safety. The project value is only used if the editor is
// null, and for our purposes, it won't be.
// This value comes from CodeStyle rather than EditorSettingsExternalizable,
val ijEditor = editor.ij
val project = ijEditor.project ?: ProjectManager.getInstance().defaultProject
return if (ijEditor.settings.isWrapWhenTypingReachesRightMargin(project)) {
ijEditor.settings.getRightMargin(ijEditor.project).asVimInt()
}
else {
VimInt.ZERO
}
}

override fun getIdeDefaultValue(editor: VimEditor): VimInt {
// Get the default value for the current language. This requires a valid project attached to the editor, which we
// won't have for the fallback window (it's really a TextComponentEditor). In this case, use a null language and
// the default right margin for
// If there's no project, we won't have a language for the editor (this will happen with the fallback window, which
// is really a TextComponentEditor). In this case, we
val ijEditor = editor.ij
val language = ijEditor.project?.let { TextEditorImpl.getDocumentLanguage(ijEditor) }
if (CodeStyle.getSettings(ijEditor).isWrapOnTyping(language)) {
return CodeStyle.getSettings(ijEditor).getRightMargin(language).asVimInt()
}
return VimInt.ZERO
}

// This function is called for all open editors, as 'textwidth' is local-to-buffer, but we set the IntelliJ setting
// as if it were local-to-window
override fun setIdeValue(editor: VimEditor, value: VimInt) {
val ijEditor = editor.ij
ijEditor.settings.setWrapWhenTypingReachesRightMargin(value.value > 0)
if (value.value > 0) {
ijEditor.settings.setRightMargin(value.value)
}
}

override fun resetDefaultValue(editor: VimEditor) {
// Reset the current settings back to default by changing both the right margin value, and the flag to wrap while
// typing. We need to use this override because we don't normally reset the right margin when disabling the flag.
// This is mainly because IntelliJ shows the hard wrap right margin visual guide by default, even when wrap while
// typing is not enabled, so resetting the default right margin would be very visible and jarring. We also don't
// want to try and control visibility of the guide with the 'textwidth' option, as the user is already used to
// IntelliJ's default behaviour of showing the guide even when wrap while typing is not enabled. Also, visibility
// of the right margin guide is tied with visibility of other visual guides, and we wouldn't know when to re-enable
// it - what if we have 'textwidth' enabled but the user doesn't want to see the guide? It's better to let the
// 'colorcolumn' option handle it. We can make sure it's always got a value of "+0" to show the 'textwidth' guide,
// and the user can disable all visual guides with `:set colorcolumn=0`.
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 value to be local only
val globalRightMargin = CodeStyle.getSettings(ijEditor).getRightMargin(language)
if (ijEditor.settings.getRightMargin(ijEditor.project) != globalRightMargin) {
ijEditor.settings.setRightMargin(globalRightMargin)
}

val globalIsWrapOnTyping = CodeStyle.getSettings(ijEditor).isWrapOnTyping(language)
if (ijEditor.settings.isWrapWhenTypingReachesRightMargin(ijEditor.project) != globalIsWrapOnTyping) {
ijEditor.settings.setWrapWhenTypingReachesRightMargin(globalIsWrapOnTyping)
}
}
}

private class WrapOptionValueProvider(wrapOption: ToggleOption) : IdeOptionValueOverride<VimInt>(wrapOption) {
override fun getIdeCurrentValue(editor: VimEditor) = getEffectiveIsUseSoftWraps(editor).asVimInt()
override fun getIdeDefaultValue(editor: VimEditor) = getGlobalIsUseSoftWraps(editor).asVimInt()

Expand Down
21 changes: 11 additions & 10 deletions src/main/resources/dictionaries/ideavim.dic
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ incsearch
iskeyword
keymodel
lookupKeys
matchpairs
mapleader
maxmapdepth
matchpairs
nrformats
relativenumber
scrolljump
Expand All @@ -31,25 +32,25 @@ sidescroll
sidescrolloff
smartcase
startofline
ideajoin
swapfile
noswapfile
timeoutlen
undolevels
viminfo
virtualedit
visualbell
wrapscan
visualdelay
wrapscan

ideacopypreprocess
ideaglobalmode
ideajoin
idearefactormode
ideastatusicon
ideastrictmode
ideawrite
ideavimsupport
maxmapdepth
ideacopypreprocess
ideatracetime
swapfile
noswapfile
ideaglobalmode
ideavimsupport
ideawrite

sethandler
packadd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class SetCommandTest : VimTestCase() {
fixture.editor.settings.isCaretRowShown = IjOptions.cursorline.defaultValue.asBoolean()
fixture.editor.settings.isUseCustomSoftWrapIndent = IjOptions.breakindent.defaultValue.asBoolean()
fixture.editor.settings.isWhitespacesShown = IjOptions.list.defaultValue.asBoolean()
fixture.editor.settings.setWrapWhenTypingReachesRightMargin(IjOptions.textwidth.defaultValue > 0)
fixture.editor.settings.setRightMargin(IjOptions.textwidth.defaultValue.value)
fixture.editor.settings.isUseSoftWraps = IjOptions.wrap.defaultValue.asBoolean()
}

Expand Down Expand Up @@ -173,18 +175,18 @@ class SetCommandTest : VimTestCase() {
|--- Options ---
|noargtextobj ideastrictmode scroll=0 notextobj-entire
|nobreakindent noideatracetime scrolljump=1 notextobj-indent
| closenotebooks ideawrite=all scrolloff=0 timeout
|nocommentary noignorecase selectmode= timeoutlen=1000
|nocursorline noincsearch shellcmdflag=-x notrackactionids
|nodigraph nolist shellxescape=@ undolevels=1000
|noexchange nomatchit shellxquote={ unifyjumps
|nogdefault maxmapdepth=20 showcmd virtualedit=
|nohighlightedyank more showmode novisualbell
| history=50 nomultiple-cursors sidescroll=0 visualdelay=100
|nohlsearch noNERDTree sidescrolloff=0 whichwrap=b,s
|noideaglobalmode nrformats=hex nosmartcase wrap
|noideajoin nonumber startofline wrapscan
| ideamarks norelativenumber nosurround
| 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
| 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 @@ -291,6 +293,7 @@ class SetCommandTest : VimTestCase() {
|nosurround
|notextobj-entire
|notextobj-indent
| textwidth=0
| timeout
| timeoutlen=1000
|notrackactionids
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class SetglobalCommandTest : VimTestCase() {
fixture.editor.settings.isCaretRowShown = IjOptions.cursorline.defaultValue.asBoolean()
fixture.editor.settings.isUseCustomSoftWrapIndent = IjOptions.breakindent.defaultValue.asBoolean()
fixture.editor.settings.isWhitespacesShown = IjOptions.list.defaultValue.asBoolean()
fixture.editor.settings.setWrapWhenTypingReachesRightMargin(IjOptions.textwidth.defaultValue > 0)
fixture.editor.settings.setRightMargin(IjOptions.textwidth.defaultValue.value)
fixture.editor.settings.isUseSoftWraps = IjOptions.wrap.defaultValue.asBoolean()
}

Expand Down Expand Up @@ -359,18 +361,18 @@ class SetglobalCommandTest : VimTestCase() {
|--- Global option values ---
|noargtextobj ideastrictmode scroll=0 notextobj-entire
|nobreakindent noideatracetime scrolljump=1 notextobj-indent
| closenotebooks ideawrite=all scrolloff=0 timeout
|nocommentary noignorecase selectmode= timeoutlen=1000
|nocursorline noincsearch shellcmdflag=-x notrackactionids
|nodigraph nolist shellxescape=@ undolevels=1000
|noexchange nomatchit shellxquote={ unifyjumps
|nogdefault maxmapdepth=20 showcmd virtualedit=
|nohighlightedyank more showmode novisualbell
| history=50 nomultiple-cursors sidescroll=0 visualdelay=100
|nohlsearch noNERDTree sidescrolloff=0 whichwrap=b,s
|noideaglobalmode nrformats=hex nosmartcase wrap
|noideajoin nonumber startofline wrapscan
| ideamarks norelativenumber nosurround
| 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
| 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 @@ -473,6 +475,7 @@ class SetglobalCommandTest : VimTestCase() {
|nosurround
|notextobj-entire
|notextobj-indent
| textwidth=0
| timeout
| timeoutlen=1000
|notrackactionids
Expand Down
Loading

0 comments on commit ebf2a97

Please sign in to comment.