├── .ci ├── install.sh ├── installer │ ├── linux.sh │ └── osx.sh └── script.sh ├── .gitignore ├── .travis.yml ├── .vintrc.yaml ├── LICENSE.md ├── README.md ├── appveyor.yml ├── autoload ├── pinkyless.vim └── pinkyless │ ├── capslock.vim │ ├── keyboard │ └── US.vim │ ├── stickyreplace.vim │ ├── stickyshift.vim │ └── util.vim ├── plugin └── pinkyless.vim └── test ├── .themisrc └── pinkyless └── util.vimspec /.ci/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | root=$(cd $(dirname $0); pwd) 4 | git config --global user.name "ci" 5 | git config --global user.email ci@example.com 6 | git clone -q --depth 1 --single-branch https://github.com/thinca/vim-themis /tmp/vim-themis 7 | git clone -q --depth 1 --single-branch https://github.com/vim-jp/vital.vim /tmp/vital 8 | PYTHONUSERBASE=$HOME/.local pip install --user vim-vint 9 | bash $root/installer/${TRAVIS_OS_NAME}.sh 10 | -------------------------------------------------------------------------------- /.ci/installer/linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | tmp=$(mktemp -d) 3 | if [[ "$VERSION" == "nvim" ]]; then 4 | url=https://github.com/neovim/neovim 5 | git clone -q --depth 1 --single-branch $url $tmp 6 | cd $tmp 7 | make -j2 CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH=$HOME/neovim" CMAKE_BUILD_TYPE=Release 8 | else 9 | url=https://github.com/vim/vim 10 | ext=$([ "$VERSION" == "HEAD" ] && echo '' || echo "-b $VERSION") 11 | git clone -q --depth 1 --single-branch $ext $url $tmp 12 | cd $tmp 13 | ./configure --prefix="$HOME/vim" \ 14 | --enable-fail-if-missing \ 15 | --with-features=huge \ 16 | --enable-perlinterp \ 17 | --enable-rubyinterp \ 18 | --enable-pythoninterp \ 19 | --enable-python3interp \ 20 | --enable-luainterp 21 | make -j2 22 | fi 23 | make install 24 | -------------------------------------------------------------------------------- /.ci/installer/osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | brew update 4 | if [[ "$VERSION" == "nvim" ]]; then 5 | brew install neovim/neovim/neovim 6 | else 7 | brew install lua 8 | brew install vim --with-lua 9 | fi 10 | -------------------------------------------------------------------------------- /.ci/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | export PATH="$HOME/neovim/bin:$HOME/vim/bin:$PATH" 4 | if [[ -d /tmp/vim-themis ]]; then 5 | export THEMIS_HOME="/tmp/vim-themis" 6 | export PATH="/tmp/vim-themis/bin:$PATH" 7 | fi 8 | if [[ "$VERSION" == "nvim" ]]; then 9 | export THEMIS_VIM="nvim" 10 | export THEMIS_ARGS="-e -s --headless" 11 | else 12 | export THEMIS_VIM="vim" 13 | fi 14 | 15 | uname -a 16 | which -a $THEMIS_VIM 17 | which -a python 18 | which -a vint 19 | which -a themis 20 | 21 | $THEMIS_VIM --version 22 | $THEMIS_VIM --cmd "try | helptags doc/ | catch | cquit | endtry" --cmd quit 23 | 24 | python --version 25 | vint --version 26 | vint autoload 27 | 28 | themis --version 29 | themis --reporter dot --runtimepath /tmp/vital 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: false 3 | env: 4 | - VERSION=HEAD 5 | - VERSION=v7.4.2137 6 | # - VERSION=nvim 7 | os: 8 | - linux 9 | - osx 10 | osx_image: xcode8 11 | 12 | addons: 13 | apt: 14 | packages: 15 | - vim 16 | - gettext 17 | - libncurses5-dev 18 | - libacl1-dev 19 | - libgpm-dev 20 | - libperl-dev 21 | - ruby-dev 22 | - python-dev 23 | - python3-dev 24 | - lua5.2 25 | - liblua5.2-dev 26 | 27 | install: 28 | - bash .ci/install.sh 29 | 30 | script: 31 | - bash .ci/script.sh 32 | -------------------------------------------------------------------------------- /.vintrc.yaml: -------------------------------------------------------------------------------- 1 | policies: 2 | ProhibitUnnecessaryDoubleQuote: 3 | enabled: false 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alisue, hashnote.net 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pinkyless.vim 2 | ============================================================================== 3 | 4 | Even Vimmer use them pinkies a lot. Rest your pinkies by using this plugin :-) 5 | 6 | 7 | Concepts 8 | ------------------------------------------------------------------------------ 9 | 10 | 1. It would be nice if Sticky shift could be used in Vim to reduce the opportunities of hitting `Shift` 11 | 2. It would be nice if the sticky shift above are also available in a single character replacement mode (`r`) 12 | 3. It would be nice if there is a software Capslock while Capslock in normal mode breaks everything. 13 | 14 | 15 | Usage 16 | ------------------------------------------------------------------------------ 17 | 18 | - Hit `;` to start sticky shift mode in Insert/Command/Select mode. 19 | In sticky shift mode, hit `` to type a trigger key (in this case, `;`) 20 | - Hit `r` to start sticky replace mode in Normal mode. 21 | In sticky replace mode, hit `;` to start the sticky shift mode. 22 | - Hit `` to start Capslock mode in Insert/Command/Select mode. 23 | In Capslock mode, hit `` again to leave or `` to enter Normal mode. 24 | 25 | The behaviours are customizable but not documented yet. 26 | 27 | 28 | Reference 29 | ------------------------------------------------------------------------------ 30 | 31 | The initial concept comes from http://vim-jp.org/vim-users-jp/2009/08/09/Hack-54.html (in Japanese) 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | clone_depth: 1 3 | environment: 4 | matrix: 5 | - VIM_URL: http://vim-jp.org/redirects/koron/vim-kaoriya/latest/win64/ 6 | install: 7 | - ps: | 8 | $zip = $Env:APPVEYOR_BUILD_FOLDER + '\vim.zip' 9 | $vim = $Env:APPVEYOR_BUILD_FOLDER + '\vim\' 10 | $redirect = Invoke-WebRequest -URI $Env:VIM_URL 11 | (New-Object Net.WebClient).DownloadFile($redirect.Links[0].href, $zip) 12 | [Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') > $null 13 | [System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $vim) 14 | $Env:THEMIS_VIM = $vim + (Get-ChildItem $vim).Name + '\vim.exe' 15 | - 'reg copy HKLM\SOFTWARE\Python\PythonCore\2.7 HKLM\SOFTWARE\Python\PythonCore\2.7-32 /s /reg:32' 16 | - 'reg copy HKLM\SOFTWARE\Python\PythonCore\2.7 HKLM\SOFTWARE\Python\PythonCore\2.7-32 /s /reg:64' 17 | - 'git clone -q --depth 1 --single-branch https://github.com/vim-jp/vital.vim %TEMP%\vital' 18 | - 'git clone -q --depth 1 --single-branch https://github.com/thinca/vim-themis %TEMP%\vim-themis' 19 | - 'git config --global user.name "Appveyor"' 20 | - 'git config --global user.email appveyor@example.com' 21 | build: off 22 | test_script: 23 | - '%THEMIS_VIM% --version' 24 | - '%TEMP%\vim-themis\bin\themis.bat --reporter dot --runtimepath %TEMP%\vital' 25 | deploy: off 26 | -------------------------------------------------------------------------------- /autoload/pinkyless.vim: -------------------------------------------------------------------------------- 1 | function! pinkyless#keyboard(...) abort 2 | let name = a:0 > 0 ? a:1 : g:pinkyless#keyboard 3 | " Use cached keyboard to reduce function calls 4 | if exists('s:keyboard_' . name) 5 | return s:keyboard_{name} 6 | endif 7 | try 8 | let s:keyboard_{name} = pinkyless#keyboard#{name}#define() 9 | catch /^Vim\%((\a\+)\)\=:E117/ 10 | " The keyboard does not exist, fallback to US keyboard 11 | let s:keyboard_{name} = pinkyless#keyboard#US#define() 12 | endtry 13 | return s:keyboard_{name} 14 | endfunction 15 | 16 | function! pinkyless#shift(char) abort 17 | let keyboard = pinkyless#keyboard(g:pinkyless#keyboard) 18 | return keyboard.shift(a:char) 19 | endfunction 20 | 21 | function! pinkyless#unshift(char) abort 22 | let keyboard = pinkyless#keyboard(g:pinkyless#keyboard) 23 | return keyboard.unshift(a:char) 24 | endfunction 25 | 26 | 27 | 28 | call pinkyless#util#define('pinkyless', { 29 | \ 'keyboard': 'US', 30 | \}) 31 | -------------------------------------------------------------------------------- /autoload/pinkyless/capslock.vim: -------------------------------------------------------------------------------- 1 | function! pinkyless#capslock#enter(mode) abort 2 | doautocmd User PinkylessCapslockEnter 3 | let b:pinkyless_capslock = 1 4 | let keyboard = pinkyless#keyboard() 5 | let keys = keyboard.keys() 6 | for key in keys 7 | let shift = keyboard.shift(key) 8 | execute printf( 9 | \ '%snoremap %s %s', 10 | \ a:mode, 11 | \ s:map_escape(key), 12 | \ s:map_escape(shift), 13 | \) 14 | execute printf( 15 | \ '%snoremap %s %s', 16 | \ a:mode, 17 | \ s:map_escape(shift), 18 | \ s:map_escape(key), 19 | \) 20 | endfor 21 | for key in keys(g:pinkyless#capslock#leave_keys) 22 | let passthrough = g:pinkyless#capslock#leave_keys[key] 23 | execute printf( 24 | \ '%smap %s pinkyless#capslock#leave(%s, %s)', 25 | \ a:mode, 26 | \ key, 27 | \ string(a:mode), 28 | \ passthrough ? string(key) : '', 29 | \) 30 | endfor 31 | return '' 32 | endfunction 33 | 34 | function! pinkyless#capslock#leave(mode, ...) abort 35 | let b:pinkyless_capslock = 0 36 | let keyboard = pinkyless#keyboard() 37 | let keys = keyboard.keys() 38 | for key in keys 39 | let shift = keyboard.shift(key) 40 | execute printf('%sunmap %s', a:mode, s:map_escape(key)) 41 | execute printf('%sunmap %s', a:mode, s:map_escape(shift)) 42 | endfor 43 | for key in keys(g:pinkyless#capslock#leave_keys) 44 | execute printf('%sunmap %s', a:mode, key) 45 | endfor 46 | doautocmd User PinkylessCapslockLeave 47 | return get(a:000, 0, '') 48 | endfunction 49 | 50 | function! pinkyless#capslock#toggle(mode) abort 51 | if get(b:, 'pinkyless_capslock') 52 | return pinkyless#capslock#leave(a:mode) 53 | else 54 | return pinkyless#capslock#enter(a:mode) 55 | endif 56 | endfunction 57 | 58 | 59 | function! s:map_escape(key) abort 60 | if a:key ==# '|' 61 | return '' 62 | elseif a:key ==# '\' 63 | return '\\' 64 | else 65 | return a:key 66 | endif 67 | endfunction 68 | 69 | " To prevent 'No such autocmd' 70 | augroup pinkyless_capslock_dummy 71 | autocmd! * 72 | autocmd User PinkylessCapslockEnter : 73 | autocmd User PinkylessCapslockLeave : 74 | augroup END 75 | 76 | call pinkyless#util#define('pinkyless#capslock', { 77 | \ 'leave_keys': { 78 | \ "\": 1, 79 | \ }, 80 | \}) 81 | -------------------------------------------------------------------------------- /autoload/pinkyless/keyboard/US.vim: -------------------------------------------------------------------------------- 1 | let s:keyboard = {} 2 | let s:keyboard.symbol_map = { 3 | \ ';': ':', 4 | \ '1': '!', '2': '@', '3': '#', '4': '$', '5': '%', 5 | \ '6': '^', '7': '&', '8': '*', '9': '(', '0': ')', 6 | \ ',': '<', '.': '>', '/': '?', '-': '_', '=': '+', 7 | \ '[': '{', ']': '}', '`': '~', "'": "\"", '\': '|', 8 | \} 9 | let s:keyboard.symbol_map_r = pinkyless#util#swap(s:keyboard.symbol_map) 10 | 11 | function! s:keyboard.shift(char) abort 12 | if a:char =~# '\l' 13 | return toupper(a:char) 14 | elseif has_key(self.symbol_map, a:char) 15 | return self.symbol_map[a:char] 16 | else 17 | return a:char 18 | endif 19 | endfunction 20 | 21 | function! s:keyboard.unshift(char) abort 22 | if a:char =~# '\L' 23 | return tolower(a:char) 24 | elseif has_key(self.symbol_map_r, a:char) 25 | return self.symbol_map_r[a:char] 26 | else 27 | return a:char 28 | endif 29 | endfunction 30 | 31 | function! s:keyboard.swap(char) abort 32 | let keys = self.keys() 33 | return index(keys, a:char) == -1 34 | \ ? self.unshift(a:char) 35 | \ : self.shift(a:char) 36 | endfunction 37 | 38 | function! s:keyboard.keys() abort 39 | let keys = split('abcdefghijklmnopqrstuvwxyz', '\zs') 40 | return extend(keys, keys(self.symbol_map)) 41 | endfunction 42 | 43 | function! s:keyboard.shift_keys() abort 44 | let keys = split('ABCDEFGHIJKLMNOPQRSTUVWXYZ', '\zs') 45 | return extend(keys, values(self.symbol_map)) 46 | endfunction 47 | 48 | 49 | function! pinkyless#keyboard#US#define() abort 50 | return s:keyboard 51 | endfunction 52 | -------------------------------------------------------------------------------- /autoload/pinkyless/stickyreplace.vim: -------------------------------------------------------------------------------- 1 | function! pinkyless#stickyreplace#enter(trigger) abort 2 | let char = nr2char(getchar()) 3 | if char ==# a:trigger 4 | let keyboard = pinkyless#keyboard() 5 | let char = pinkyless#stickyshift#enter(a:trigger) 6 | endif 7 | let line = getline('.') 8 | let col = col('.') 9 | let lhs = col == 1 ? '' : line[:col-2] 10 | let rhs = col == 1 ? line[1:] : line[col:] 11 | call setline('.', lhs . char . rhs) 12 | endfunction 13 | -------------------------------------------------------------------------------- /autoload/pinkyless/stickyshift.vim: -------------------------------------------------------------------------------- 1 | " Ref: http://vim-jp.org/vim-users-jp/2009/08/09/Hack-54.html 2 | function! pinkyless#stickyshift#enter(trigger) abort 3 | doautocmd User PinkylessStickyShiftEnter 4 | let char = nr2char(getchar()) 5 | doautocmd User PinkylessStickyShiftLeave 6 | if has_key(g:pinkyless#stickyshift#substitutions, char) 7 | let char = g:pinkyless#stickyshift#substitutions[char] 8 | return substitute(char, '', a:trigger, 'g') 9 | endif 10 | let keyboard = pinkyless#keyboard() 11 | return keyboard.swap(char) 12 | endfunction 13 | 14 | " To prevent 'No such autocmd' 15 | augroup pinkyless_stickyshift_dummy 16 | autocmd! * 17 | autocmd User PinkylessStickyShiftEnter : 18 | autocmd User PinkylessStickyShiftLeave : 19 | augroup END 20 | 21 | call pinkyless#util#define('pinkyless#stickyshift', { 22 | \ 'substitutions': { 23 | \ "\": "", 24 | \ "\": "\", 25 | \ }, 26 | \}) 27 | -------------------------------------------------------------------------------- /autoload/pinkyless/util.vim: -------------------------------------------------------------------------------- 1 | function! pinkyless#util#swap(d) abort 2 | let ks = keys(a:d) 3 | let vs = values(a:d) 4 | let swap = {} 5 | for i in range(len(ks)) 6 | let swap[vs[i]] = ks[i] 7 | endfor 8 | return swap 9 | endfunction 10 | 11 | function! pinkyless#util#define(prefix, default) abort 12 | let prefix = a:prefix =~# '^g:' ? a:prefix : 'g:' . a:prefix 13 | for [key, Value] in items(a:default) 14 | let name = prefix . '#' . key 15 | if !exists(name) 16 | execute 'let ' . name . ' = ' . string(Value) 17 | endif 18 | unlet Value 19 | endfor 20 | endfunction 21 | -------------------------------------------------------------------------------- /plugin/pinkyless.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_pinkeyless') 2 | finish 3 | endif 4 | let g:loaded_pinkyless = 1 5 | 6 | " Default configurations 7 | let g:pinkyless_capslock_trigger = 8 | \ get(g:, 'pinkyless_capslock_trigger', '') 9 | 10 | let g:pinkyless_stickyshift_trigger = 11 | \ get(g:, 'pinkyless_stickyshift_trigger', ';') 12 | 13 | let g:pinkyless_stickyreplace_trigger = 14 | \ get(g:, 'pinkyless_stickyreplace_trigger', 'r') 15 | 16 | let g:pinkyless_capslock_i = get(g:, 'pinkyless_capslock_i', 1) 17 | let g:pinkyless_capslock_c = get(g:, 'pinkyless_capslock_c', 1) 18 | let g:pinkyless_capslock_s = get(g:, 'pinkyless_capslock_s', 1) 19 | 20 | let g:pinkyless_stickyshift_i = get(g:, 'pinkyless_stickyshift_i', 1) 21 | let g:pinkyless_stickyshift_c = get(g:, 'pinkyless_stickyshift_c', 1) 22 | let g:pinkyless_stickyshift_s = get(g:, 'pinkyless_stickyshift_s', 1) 23 | 24 | let g:pinkyless_stickyreplace_n = get(g:, 'pinkyless_stickyreplace_n', 1) 25 | 26 | 27 | " Capslock mappings 28 | inoremap (pinkyless-capslock-enter) 29 | \ pinkyless#capslock#enter('i') 30 | inoremap (pinkyless-capslock-leave) 31 | \ pinkyless#capslock#leave('i') 32 | inoremap (pinkyless-capslock-toggle) 33 | \ pinkyless#capslock#toggle('i') 34 | cnoremap (pinkyless-capslock-enter) 35 | \ pinkyless#capslock#enter('c') 36 | cnoremap (pinkyless-capslock-leave) 37 | \ pinkyless#capslock#leave('c') 38 | cnoremap (pinkyless-capslock-toggle) 39 | \ pinkyless#capslock#toggle('c') 40 | snoremap (pinkyless-capslock-enter) 41 | \ pinkyless#capslock#enter('s') 42 | snoremap (pinkyless-capslock-leave) 43 | \ pinkyless#capslock#leave('s') 44 | snoremap (pinkyless-capslock-toggle) 45 | \ pinkyless#capslock#toggle('s') 46 | 47 | " Sticky shift mappings 48 | inoremap (pinkyless-stickyshift-enter) 49 | \ pinkyless#stickyshift#enter(g:pinkyless_stickyshift_trigger) 50 | cnoremap (pinkyless-stickyshift-enter) 51 | \ pinkyless#stickyshift#enter(g:pinkyless_stickyshift_trigger) 52 | snoremap (pinkyless-stickyshift-enter) 53 | \ pinkyless#stickyshift#enter(g:pinkyless_stickyshift_trigger) 54 | 55 | " Sticky replace mapping 56 | nnoremap (pinkyless-stickyreplace-enter) 57 | \ :call pinkyless#stickyreplace#enter(g:pinkyless_stickyshift_trigger) 58 | 59 | 60 | if get(g:, 'pinkyless_default_keymap', 1) 61 | " Capslock 62 | if g:pinkyless_capslock_i 63 | execute printf( 64 | \ 'imap %s (pinkyless-capslock-toggle)', 65 | \ g:pinkyless_capslock_trigger, 66 | \) 67 | endif 68 | if g:pinkyless_capslock_c 69 | execute printf( 70 | \ 'cmap %s (pinkyless-capslock-toggle)', 71 | \ g:pinkyless_capslock_trigger, 72 | \) 73 | endif 74 | if g:pinkyless_capslock_s 75 | execute printf( 76 | \ 'smap %s (pinkyless-capslock-toggle)', 77 | \ g:pinkyless_capslock_trigger, 78 | \) 79 | endif 80 | 81 | " Sticky shit 82 | if g:pinkyless_stickyshift_i 83 | execute printf( 84 | \ 'imap %s (pinkyless-stickyshift-enter)', 85 | \ g:pinkyless_stickyshift_trigger, 86 | \) 87 | endif 88 | if g:pinkyless_stickyshift_c 89 | execute printf( 90 | \ 'cmap %s (pinkyless-stickyshift-enter)', 91 | \ g:pinkyless_stickyshift_trigger, 92 | \) 93 | endif 94 | if g:pinkyless_stickyshift_s 95 | execute printf( 96 | \ 'smap %s (pinkyless-stickyshift-enter)', 97 | \ g:pinkyless_stickyshift_trigger, 98 | \) 99 | endif 100 | 101 | " Sticky replace 102 | if g:pinkyless_stickyreplace_n 103 | execute printf( 104 | \ 'nmap %s (pinkyless-stickyreplace-enter)', 105 | \ g:pinkyless_stickyreplace_trigger, 106 | \) 107 | endif 108 | endif 109 | -------------------------------------------------------------------------------- /test/.themisrc: -------------------------------------------------------------------------------- 1 | let s:assert = themis#helper('assert') 2 | call themis#option('recursive', 1) 3 | call themis#option('reporter', 'dot') 4 | call themis#helper('command').with(s:assert) 5 | -------------------------------------------------------------------------------- /test/pinkyless/util.vimspec: -------------------------------------------------------------------------------- 1 | Describe pinkyless#util 2 | Describe #swap() 3 | It swaps a dictionary keys and values 4 | let d = {'a': 'A', 'b': 'B', 'c': 'C'} 5 | let s = pinkyless#util#swap(d) 6 | Assert Equals(s, {'A': 'a', 'B': 'b', 'C': 'c'}) 7 | End 8 | End 9 | End 10 | --------------------------------------------------------------------------------