Vim configuration
Simple
The simplest Vim configuration you can start with is as follows:
import Yi
main = yi $ defaultVimConfig
Basically, importing Yi
imports all the modules required to get started. defaultVimConfig
sets the default Vim config.
Let’s first refactor our config.
import Yi
main = yi $ myConfig
myConfig = defaultVimConfig
Now, the nice part about being extensible in Haskell, is the ease of configuration. defaultVimConfiguration
uses Haskell’s record syntax. Don’t worry if you don’t understand what that means. Basically, you can express the configuration more verbosely as follows:
import Yi
main = yi $ myConfig
myConfig = defaultVimConfig
{ defaultKm = defaultKm defaultVimConfig
, configUI = configUI defaultVimConfig
}
We haven’t really changed anything, but now we’re being more explicit about the fields in the config file. We’ve only listed two fields, defaultKm
which sets the keymap, and configUI
which sets the UI. There are actually many more fields to defaultVimConfig, but right now we only care about these two fields.
Theme
Let’s make a small change. We want our UI to look more like Vim’s, so we edit the configUI field:
import Yi
main = yi $ myConfig
myConfig = defaultVimConfig
{ defaultKm = defaultKm defaultVimConfig
, configUI = (configUI defaultVimConfig) { configWindowFill = '~' }
}
The parenthesis there was needed to make sure the value of configUI is modified. Again, we’re able to edit configWindowFill
thanks to Haskell’s record syntax.
Now we have ~ signs on every line except the first. Feels much more like vim. But we can do better.
import Yi
import Yi.Style (Color(Default))
import Data.Monoid ((<>))
main = yi $ myConfig
myConfig = defaultVimConfig
{ defaultKm = defaultKm defaultVimConfig
, configUI = (configUI defaultVimConfig) { configWindowFill = '~'
, configTheme = myTheme
}
}
defaultColor :: Yi.Style.Color
defaultColor = Yi.Style.Default
myTheme = defaultTheme `override` \super _ -> super
{ modelineAttributes = emptyAttributes { foreground = black, background = darkcyan }
, tabBarAttributes = emptyAttributes { foreground = white, background = defaultColor }
, baseAttributes = emptyAttributes { foreground = defaultColor, background = defaultColor, bold = True }
, commentStyle = withFg darkred <> withBd False <> withItlc True
, selectedStyle = withReverse True
, errorStyle = withBg red <> withFg white
, operatorStyle = withFg brown <> withBd False
, hintStyle = withBg brown <> withFg black
, importStyle = withFg blue
, dataConstructorStyle = withFg blue
, typeStyle = withFg blue
, keywordStyle = withFg yellow
, builtinStyle = withFg brown
, strongHintStyle = withBg brown <> withUnderline True
, stringStyle = withFg brown <> withBd False
, preprocessorStyle = withFg blue
}
That should add a nice looking theme. You can of course customize the theme as you like.
Now, finally, the most useful part. Custom keybindings. Vim users typically use nmap and imap to define custom keybindings. So let’s deine nmap and imap ourselves, call those definitions part of boilerplate, and concentrate on what’s important.
{-# LANGUAGE OverloadedStrings #-}
import Yi
import qualified Yi.Keymap.Vim as V2
import qualified Yi.Keymap.Vim.Common as V2
import qualified Yi.Keymap.Vim.Utils as V2
import Data.Monoid ((<>), mappend)
main = yi $ myConfig
myConfig = defaultVimConfig
{ defaultKm = myKeymap
, configUI = (configUI defaultVimConfig) { configWindowFill = '~' }
}
myKeymap = v2KeymapSet $ myBindings
myBindings :: (V2.EventString -> EditorM ()) -> [V2.VimBinding]
myBindings eval =
[ nmap "<C-h>" previousTabE
, nmap "<C-l>" nextTabE
-- Press space to clear incremental search highlight
, nmap " " (eval ":nohlsearch<CR>")
-- for times when you don't press shift hard enough
, nmap ";" (eval ":")
-- Vim's behavior of Y, for historical reasons.
, nmap "Y" (eval "yy")
, nmap "<F3>" (withCurrentBuffer deleteTrailingSpaceB)
, nmap "<F4>" (withCurrentBuffer moveToSol)
, imap "<Home>" (withCurrentBuffer moveToSol)
, imap "<End>" (withCurrentBuffer moveToEol)
]
-- Boilerplate begins here
v2KeymapSet :: ((V2.EventString -> EditorM ()) -> [V2.VimBinding]) -> KeymapSet
v2KeymapSet myBindings = V2.mkKeymapSet $ V2.defVimConfig `override` \super this ->
let eval = V2.pureEval this
in super {
V2.vimBindings = myBindings eval <> V2.vimBindings super
}
nmap x y = V2.mkStringBindingE V2.Normal V2.Drop (x, y, id)
imap x y = V2.VimBindingE (\evs state -> case V2.vsMode state of
V2.Insert _ ->
fmap (const (y >> return V2.Continue))
(evs `V2.matchesString` x)
_ -> V2.NoMatch)
nmap' x y = V2.mkStringBindingY V2.Normal (x, y, id)
leader :: V2.EventString -> V2.EventString
leader = mappend "\\"
--Boilerplate ends here
And that’s it. The nmap’s and imap’s look exactly like Vim’s config! You don’t need to bother about how nmap and imap are defined. With a little bit of boilerplate, you can start configuring Yi just like Vim.
The above config (including the theme) is a simplified version of Michal’s config
Extending Vim numbers
Just to show you how easy it is to configure Yi, consider the following case.
You want to modify the behavior of <C-a>
in Vim by making the cursor stay at the same location, while still incrementing the number. You went ahead and looked at the source code for <C-a>
which gave you getCountE >>= withCurrentBuffer . incrementNextNumberByB
. Using this, you can simple use function composition, to add savingPointB
which saves the cursor location and makes sure the cursor moves back to that location after the function is executed. In our case, the function incrementNextNumberByB
is what gets executed, and that’s the function responsible for moving the cursor.
{-# LANGUAGE OverloadedStrings #-}
import Yi
import qualified Yi.Keymap.Vim as V2
import qualified Yi.Keymap.Vim.Common as V2
import qualified Yi.Keymap.Vim.Utils as V2
import Yi.Keymap.Vim.StateUtils (getCountE)
import Data.Monoid ((<>), mappend)
main = yi $ myConfig
myConfig = defaultVimConfig
{ defaultKm = myKeymap
, configUI = (configUI defaultVimConfig) { configWindowFill = '~' }
}
myKeymap = v2KeymapSet $ myBindings
myBindings :: (V2.EventString -> EditorM ()) -> [V2.VimBinding]
myBindings eval =
[ nmap (leader "<C-a>") (getCountE >>= withCurrentBuffer . savingPointB . incrementNextNumberByB)
, nmap (leader "<C-x>") (getCountE >>= withCurrentBuffer . savingPointB . incrementNextNumberByB . negate)
]
-- Boilerplate begins here
v2KeymapSet :: ((V2.EventString -> EditorM ()) -> [V2.VimBinding]) -> KeymapSet
v2KeymapSet myBindings = V2.mkKeymapSet $ V2.defVimConfig `override` \super this ->
let eval = V2.pureEval this
in super {
V2.vimBindings = myBindings eval <> V2.vimBindings super
}
nmap x y = V2.mkStringBindingE V2.Normal V2.Drop (x, y, id)
imap x y = V2.VimBindingE (\evs state -> case V2.vsMode state of
V2.Insert _ ->
fmap (const (y >> return V2.Continue))
(evs `V2.matchesString` x)
_ -> V2.NoMatch)
nmap' x y = V2.mkStringBindingY V2.Normal (x, y, id)
leader :: V2.EventString -> V2.EventString
leader = mappend "\\"
--Boilerplate ends here
That’s how easy configuring Yi is. Simple function composition (of course, you should not mind looking at the source code first).
Removing Boilerplate
See the section, Modularizing the config, for details on moving the boilerplate outside the yi.hs config file. In short, files that need to be imported can be placed in the .config/yi/lib
directory.