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 10, 2024
1 parent 8df233e commit 21599fb
Show file tree
Hide file tree
Showing 9 changed files with 567 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
84 changes: 76 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,67 @@ 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. Because we're encapsulating both "enabled" and "wrap column" in a
// single integer value, we don't have the context to reset the "wrap column" when wrapping while typing is disabled
// This is only important because the default for IntelliJ is to always show the wrap guide, even if hard wrap is
// disabled. Once implemented, the 'colorcolumn' setting can be used to show/hide this guide (setting it to "+0"
// would match the "show right margin" setting. Implementing "+1", "-1", etc. might be more tricky)
val ijEditor = editor.ij
val language = ijEditor.project?.let { TextEditorImpl.getDocumentLanguage(ijEditor) }
ijEditor.settings.setRightMargin(CodeStyle.getSettings(ijEditor).getRightMargin(language))
ijEditor.settings.setWrapWhenTypingReachesRightMargin(CodeStyle.getSettings(ijEditor).isWrapOnTyping(language))
}
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class SetlocalCommandTest : 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 @@ -392,17 +394,17 @@ class SetlocalCommandTest : VimTestCase() {
|noargtextobj idearefactormode= norelativenumber nosurround
|nobreakindent ideastrictmode scroll=0 notextobj-entire
| closenotebooks noideatracetime scrolljump=1 notextobj-indent
|nocommentary ideawrite=all scrolloff=-1 timeout
|nocursorline noignorecase selectmode= timeoutlen=1000
|nodigraph noincsearch shellcmdflag=-x notrackactionids
|noexchange nolist shellxescape=@ unifyjumps
|nogdefault nomatchit shellxquote={ virtualedit=
|nohighlightedyank maxmapdepth=20 showcmd novisualbell
| history=50 more showmode visualdelay=100
|nohlsearch nomultiple-cursors sidescroll=0 whichwrap=b,s
|noideaglobalmode noNERDTree sidescrolloff=-1 wrap
|--ideajoin nrformats=hex nosmartcase wrapscan
| ideamarks nonumber startofline
|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
| 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 @@ -511,6 +513,7 @@ class SetlocalCommandTest : VimTestCase() {
|nosurround
|notextobj-entire
|notextobj-indent
| textwidth=0
| timeout
| timeoutlen=1000
|notrackactionids
Expand Down
Loading

0 comments on commit 21599fb

Please sign in to comment.