made the pack completely portable and wrote relevent bat files to go with it

This commit is contained in:
Draqoken
2025-04-09 17:04:56 +03:00
parent 5e77d7e9cf
commit 5e4144c3c0
7417 changed files with 2181044 additions and 19 deletions

View File

@@ -0,0 +1,72 @@
vim9script
# cfilter.vim: Plugin to filter entries from a quickfix/location list
# Last Change: August 16, 2023
# Maintainer: Yegappan Lakshmanan (yegappan AT yahoo DOT com)
# Version: 2.0
#
# Commands to filter the quickfix list:
# :Cfilter[!] /{pat}/
# Create a new quickfix list from entries matching {pat} in the current
# quickfix list. Both the file name and the text of the entries are
# matched against {pat}. If ! is supplied, then entries not matching
# {pat} are used. The pattern can be optionally enclosed using one of
# the following characters: ', ", /. If the pattern is empty, then the
# last used search pattern is used.
# :Lfilter[!] /{pat}/
# Same as :Cfilter but operates on the current location list.
#
def Qf_filter(qf: bool, searchpat: string, bang: string)
var Xgetlist: func
var Xsetlist: func
var cmd: string
var firstchar: string
var lastchar: string
var pat: string
var title: string
var Cond: func
var items: list<any>
if qf
Xgetlist = function('getqflist')
Xsetlist = function('setqflist')
cmd = $':Cfilter{bang}'
else
Xgetlist = function('getloclist', [0])
Xsetlist = function('setloclist', [0])
cmd = $':Lfilter{bang}'
endif
firstchar = searchpat[0]
lastchar = searchpat[-1 :]
if firstchar == lastchar &&
(firstchar == '/' || firstchar == '"' || firstchar == "'")
pat = searchpat[1 : -2]
if pat == ''
# Use the last search pattern
pat = @/
endif
else
pat = searchpat
endif
if pat == ''
return
endif
if bang == '!'
Cond = (_, val) => val.text !~# pat && bufname(val.bufnr) !~# pat
else
Cond = (_, val) => val.text =~# pat || bufname(val.bufnr) =~# pat
endif
items = filter(Xgetlist(), Cond)
title = $'{cmd} /{pat}/'
Xsetlist([], ' ', {title: title, items: items})
enddef
command! -nargs=+ -bang Cfilter Qf_filter(true, <q-args>, <q-bang>)
command! -nargs=+ -bang Lfilter Qf_filter(false, <q-args>, <q-bang>)
# vim: shiftwidth=2 sts=2 expandtab

View File

@@ -0,0 +1,78 @@
vim9script
# Maintainer: Maxim Kim <habamax@gmail.com>
# Last Update: 2024 Oct 05
#
# Toggle comments
# Usage:
# Add following mappings to vimrc:
# import autoload 'dist/comment.vim'
# nnoremap <silent> <expr> gc comment.Toggle()
# xnoremap <silent> <expr> gc comment.Toggle()
# nnoremap <silent> <expr> gcc comment.Toggle() .. '_'
# nnoremap <silent> <expr> gC comment.Toggle() .. '$'
export def Toggle(...args: list<string>): string
if len(args) == 0
&opfunc = matchstr(expand('<stack>'), '[^. ]*\ze[')
return 'g@'
endif
if empty(&cms) || !&ma | return '' | endif
var cms = substitute(substitute(&cms, '\S\zs%s\s*', ' %s', ''), '%s\ze\S', '%s ', '')
var [lnum1, lnum2] = [line("'["), line("']")]
var cms_l = split(escape(cms, '*.'), '\s*%s\s*')
var first_col = indent(lnum1)
var start_col = getpos("'[")[2] - 1
if len(cms_l) == 1 && lnum1 == lnum2 && first_col < start_col
var line_start = getline(lnum1)[0 : start_col - 1]
var line_end = getline(lnum1)[start_col : -1]
line_end = line_end =~ $'^\s*{cms_l[0]}' ?
\ substitute(line_end, $'^\s*\zs{cms_l[0]}\s\ze\s*', line_end =~ '^\s' ? ' ' : '', '') :
\ printf(substitute(cms, '%s\@!', '%%', ''), line_end)
setline(lnum1, line_start .. line_end)
return ''
endif
if len(cms_l) == 0 | return '' | endif
if len(cms_l) == 1 | call add(cms_l, '') | endif
var comment = false
var indent_spaces = false
var indent_tabs = false
var indent_min = indent(lnum1)
var indent_start = matchstr(getline(lnum1), '^\s*')
for lnum in range(lnum1, lnum2)
if getline(lnum) =~ '^\s*$' | continue | endif
var indent_str = matchstr(getline(lnum), '^\s*')
if indent_min > indent(lnum)
indent_min = indent(lnum)
indent_start = indent_str
endif
indent_spaces = indent_spaces || (stridx(indent_str, ' ') != -1)
indent_tabs = indent_tabs || (stridx(indent_str, "\t") != -1)
if getline(lnum) !~ $'^\s*{cms_l[0]}.*{cms_l[1]}$'
comment = true
endif
endfor
var mixed_indent = indent_spaces && indent_tabs
var lines = []
var line = ''
for lnum in range(lnum1, lnum2)
if getline(lnum) =~ '^\s*$'
line = getline(lnum)
elseif comment
if exists("g:comment_first_col") || exists("b:comment_first_col")
line = printf(substitute(cms, '%s\@!', '%%', 'g'), getline(lnum))
else
# consider different whitespace indenting
var indent_current = mixed_indent ? matchstr(getline(lnum), '^\s*') : indent_start
line = printf(indent_current .. substitute(cms, '%s\@!', '%%', 'g'),
strpart(getline(lnum), strlen(indent_current)))
endif
else
line = substitute(getline(lnum), $'^\s*\zs{cms_l[0]} \?\| \?{cms_l[1]}$', '', 'g')
endif
add(lines, line)
endfor
noautocmd keepjumps setline(lnum1, lines)
return ''
enddef

View File

@@ -0,0 +1,80 @@
*comment.txt* For Vim version 9.1. Last change: 2024 Oct 01
VIM REFERENCE MANUAL
Commenting and un-commenting text.
==============================================================================
See |comment-install| on how to activate this package.
The comment.vim package, allows to toggle comments for a single line, a range
of lines or a selected text object. It defines the following mappings:
*o_gc*
gc{motion} to toggle comments for the selected motion
*v_gc*
{Visual}gc to comment/uncomment the highlighted lines.
Since gc operates on a motion, it can be used with any motion, for example _
to comment the current line, or ip to comment the current paragraph.
A default mapping `gcc` to `gc_` is defined:
*gcc*
gcc to comment/uncomment current line
To comment the rest of the line by `gC` whenever the filetype plugin
supports it (that is, whenever the comment marker precedes the code) and fall
back to `gcc` otherwise, add the following mapping to your vimrc: >
nnoremap <silent> <expr> gC comment#Toggle() .. '$'
<
Note: using `gC` may not always result in valid comment markers depending on
the language used.
This plugin uses the buffer-local 'commentstring' option value to add or remove
comment markers to the selected lines. Whether it will comment or un-comment
depends on the first line of the range of lines to act upon. When it matches
a comment marker, the line will be un-commented, if it doesn't, the line will
be commented out. Blank and empty lines are ignored.
The value of 'commentstring' is the same for the entire buffer and determined
by its filetype (|filetypes|). To adapt it within the buffer for embedded
languages, you can use a plug-in such as
https://github.com/suy/vim-context-commentstring.
The comment marker will always be padded with blanks whether or not the
'commentstring' value contains whitespace around "%s".
If the mapping does not seem to work (or uses wrong comment markers), it might
be because of several reasons:
- the filetype is not detected by Vim, see |new-filetype|,
- filetype plugins are not enabled, see |:filetype-plugin-on| or
- the filetype plugin does not set the (correct) 'commentstring' option.
You can simply configure this using the following autocommand (e.g. for legacy
Vim script): >
autocmd Filetype vim :setlocal commentstring="%s
This example sets the " as start of a comment for legacy Vim script. For Vim9
script, you would instead use the "#" char: >
autocmd Filetype vim :setlocal commentstring=#\ %s
==============================================================================
Options:
*g:comment_first_col*
*b:comment_first_col*
By default comment chars are added in front of the line, i.e. if the line
was indented, commented line would stay indented as well.
However some filetypes require a comment char on the first column, use this option
to change default behaviour.
Use g:comment_first_col to change it globally or b:comment_first_col to
target specific filetype(s).
==============================================================================
vim:tw=78:ts=8:fo=tcq2:ft=help:

View File

@@ -0,0 +1,6 @@
b:comment_first_col comment.txt /*b:comment_first_col*
comment.txt comment.txt /*comment.txt*
g:comment_first_col comment.txt /*g:comment_first_col*
gcc comment.txt /*gcc*
o_gc comment.txt /*o_gc*
v_gc comment.txt /*v_gc*

View File

@@ -0,0 +1,9 @@
vim9script
# Maintainer: Maxim Kim <habamax@gmail.com>
# Last Update: 2024-04-26
import autoload 'comment.vim'
nnoremap <silent> <expr> gc comment.Toggle()
xnoremap <silent> <expr> gc comment.Toggle()
nnoremap <silent> <expr> gcc comment.Toggle() .. '_'

View File

@@ -0,0 +1,72 @@
" Back to Qwerty keyboard after using Dvorak.
iunmap a
iunmap b
iunmap c
iunmap d
iunmap e
iunmap f
iunmap g
iunmap h
iunmap i
iunmap j
iunmap k
iunmap l
iunmap m
iunmap n
iunmap o
iunmap p
iunmap q
iunmap r
iunmap s
iunmap t
iunmap u
iunmap v
iunmap w
iunmap x
iunmap y
iunmap z
iunmap ;
iunmap '
iunmap "
iunmap ,
iunmap .
iunmap /
iunmap A
iunmap B
iunmap C
iunmap D
iunmap E
iunmap F
iunmap G
iunmap H
iunmap I
iunmap J
iunmap K
iunmap L
iunmap M
iunmap N
iunmap O
iunmap P
iunmap Q
iunmap R
iunmap S
iunmap T
iunmap U
iunmap V
iunmap W
iunmap X
iunmap Y
iunmap Z
iunmap <
iunmap >
iunmap ?
iunmap :
iunmap [
iunmap ]
iunmap {
iunmap }
iunmap -
iunmap _
iunmap =
iunmap +

View File

@@ -0,0 +1,77 @@
" Dvorak keyboard, only in Insert mode.
"
" Change "inoremap" to "map!" to also use in Ex mode.
" Also change disable.vim then: "iunmap" to "unmap!".
"
" You may want to add a list of map's too.
inoremap a a
inoremap b x
inoremap c j
inoremap d e
inoremap e .
inoremap f u
inoremap g i
inoremap h d
inoremap i c
inoremap j h
inoremap k t
inoremap l n
inoremap m m
inoremap n b
inoremap o r
inoremap p l
inoremap q '
inoremap r p
inoremap s o
inoremap t y
inoremap u g
inoremap v k
inoremap w ,
inoremap x q
inoremap y f
inoremap z ;
inoremap ; s
inoremap ' -
inoremap " _
inoremap , w
inoremap . v
inoremap / z
inoremap A A
inoremap B X
inoremap C J
inoremap D E
inoremap E >
inoremap F U
inoremap G I
inoremap H D
inoremap I C
inoremap J H
inoremap K T
inoremap L N
inoremap M M
inoremap N B
inoremap O R
inoremap P L
inoremap Q "
inoremap R P
inoremap S O
inoremap T Y
inoremap U G
inoremap V K
inoremap W <
inoremap X Q
inoremap Y F
inoremap Z :
inoremap < W
inoremap > V
inoremap ? Z
inoremap : S
inoremap [ /
inoremap ] =
inoremap { ?
inoremap } +
inoremap - [
inoremap _ {
inoremap = ]
inoremap + }

View File

@@ -0,0 +1,16 @@
" When using a dvorak keyboard this file may be of help to you.
" These mappings have been made by Lawrence Kesteloot <kesteloo@cs.unc.edu>.
" What they do is that the most often used keys, like hjkl, are put in a more
" easy to use position.
" It may take some time to learn using this.
if exists("g:loaded_dvorak_plugin")
finish
endif
let g:loaded_dvorak_plugin = 1
" Key to go into dvorak mode:
map ,d :runtime dvorak/enable.vim<CR>
" Key to get out of dvorak mode:
map ,q :runtime dvorak/disable.vim<CR>

View File

@@ -0,0 +1,118 @@
" Vim Plugin: Edit the file with an existing Vim if possible
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2023 Aug 13
" To use add ":packadd! editexisting" in your vimrc file.
" This plugin serves two purposes:
" 1. On startup, if we were invoked with one file name argument and the file
" is not modified then try to find another Vim instance that is editing
" this file. If there is one then bring it to the foreground and exit.
" 2. When a file is edited and a swap file exists for it, try finding that
" other Vim and bring it to the foreground. Requires Vim 7, because it
" uses the SwapExists autocommand event.
" Function that finds the Vim instance that is editing "filename" and brings
" it to the foreground.
func s:EditElsewhere(filename)
let fname_esc = substitute(a:filename, "'", "''", "g")
let servers = serverlist()
while servers != ''
" Get next server name in "servername"; remove it from "servers".
let i = match(servers, "\n")
if i == -1
let servername = servers
let servers = ''
else
let servername = strpart(servers, 0, i)
let servers = strpart(servers, i + 1)
endif
" Skip ourselves.
if servername ==? v:servername
continue
endif
" Check if this server is editing our file.
try
if remote_expr(servername, "bufloaded('" . fname_esc . "')")
" Yes, bring it to the foreground.
if has("win32")
call remote_foreground(servername)
endif
call remote_expr(servername, "foreground()")
if remote_expr(servername, "exists('*EditExisting')")
" Make sure the file is visible in a window (not hidden).
" If v:swapcommand exists and is set, send it to the server.
if exists("v:swapcommand")
let c = substitute(v:swapcommand, "'", "''", "g")
call remote_expr(servername, "EditExisting('" . fname_esc . "', '" . c . "')")
else
call remote_expr(servername, "EditExisting('" . fname_esc . "', '')")
endif
endif
if !(has('vim_starting') && has('gui_running') && has('gui_win32'))
" Tell the user what is happening. Not when the GUI is starting
" though, it would result in a message box.
echomsg "File is being edited by " . servername
sleep 2
endif
return 'q'
endif
catch /^Vim\%((\a\+)\)\=:E241:/
" Unable to send to this server, ignore it.
endtry
endwhile
return ''
endfunc
" When the plugin is loaded and there is one file name argument: Find another
" Vim server that is editing this file right now.
if argc() == 1 && !&modified
if s:EditElsewhere(expand("%:p")) == 'q'
quit
endif
endif
" Setup for handling the situation that an existing swap file is found.
try
au! SwapExists * let v:swapchoice = s:EditElsewhere(expand("<afile>:p"))
catch
" Without SwapExists we don't do anything for ":edit" commands
endtry
" Function used on the server to make the file visible and possibly execute a
" command.
func! EditExisting(fname, command)
" Get the window number of the file in the current tab page.
let winnr = bufwinnr(a:fname)
if winnr <= 0
" Not found, look in other tab pages.
let bufnr = bufnr(a:fname)
for i in range(tabpagenr('$'))
if index(tabpagebuflist(i + 1), bufnr) >= 0
" Make this tab page the current one and find the window number.
exe 'tabnext ' . (i + 1)
let winnr = bufwinnr(a:fname)
break
endif
endfor
endif
if winnr > 0
exe winnr . "wincmd w"
elseif exists('*fnameescape')
exe "split " . fnameescape(a:fname)
else
exe "split " . escape(a:fname, " \t\n*?[{`$\\%#'\"|!<")
endif
if a:command != ''
exe "normal! " . a:command
endif
redraw
endfunc

View File

@@ -0,0 +1,27 @@
root = true
[*]
end_of_line = lf
charset = utf-8
max_line_length = 80
[*.{vim,sh}]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 80
[*.rb]
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
[*.yml]
indent_style = space
indent_size = 2
[*.{bat,vbs,ps1}]
end_of_line = CRLF

View File

@@ -0,0 +1,6 @@
Contributors to the EditorConfig Vim Plugin:
Hong Xu
Trey Hunner
Kent Frazier
Chris White

View File

@@ -0,0 +1,26 @@
Unless otherwise stated, all files are distributed under the Simplified BSD
license included below.
Copyright (c) 2011-2019 EditorConfig Team
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,53 @@
Some code in editorconfig-vim is derived from code licensed under the
PSF license. The following is the text of that license, retrieved 2019-05-05
from https://docs.python.org/2.6/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
PSF LICENSE AGREEMENT FOR PYTHON 2.6.9
1. This LICENSE AGREEMENT is between the Python Software Foundation
(``PSF''), and the Individual or Organization (``Licensee'') accessing and
otherwise using Python 2.6.9 software in source or binary form and its
associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 2.6.9
alone or in any derivative version, provided, however, that PSF's
License Agreement and PSF's notice of copyright, i.e., ``Copyright (c)
2001-2010 Python Software Foundation; All Rights Reserved'' are
retained in Python 2.6.9 alone or in any derivative version prepared
by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 2.6.9 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 2.6.9.
4. PSF is making Python 2.6.9 available to Licensee on an ``AS IS''
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY
REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY
PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.6.9 WILL NOT INFRINGE
ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
2.6.9 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.6.9,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python 2.6.9, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
# vi: set ft=:

View File

@@ -0,0 +1,148 @@
# EditorConfig Vim Plugin
[![Travis Build Status](https://img.shields.io/travis/cxw42/editorconfig-vim.svg?logo=travis)](https://travis-ci.org/editorconfig/editorconfig-vim)
[![Appveyor Build Status](https://img.shields.io/appveyor/ci/cxw42/editorconfig-vim.svg?logo=appveyor)](https://ci.appveyor.com/project/cxw42/editorconfig-vim)
This is an [EditorConfig][] plugin for Vim. This plugin can be found on both
[GitHub][] and [Vim online][].
## Installation
To install this plugin, you can use one of the following ways:
### Install with the archive
Download the [archive][] and extract it into your Vim runtime directory
(`~/.vim` on UNIX/Linux and `$VIM_INSTALLATION_FOLDER\vimfiles` on windows).
You should have 4 sub-directories in this runtime directory now: "autoload",
"doc", "ftdetect" and "plugin".
### Install as Vim8 plugin
Install as a Vim 8 plugin. Note `local` can be any name, but some path
element must be present. On Windows, instead of `~/.vim` use
`$VIM_INSTALLATION_FOLDER\vimfiles`.
```shell
mkdir -p ~/.vim/pack/local/start
cd ~/.vim/pack/local/start
git clone https://github.com/editorconfig/editorconfig-vim.git
```
### Install with [pathogen][]
Use pathogen (the git repository of this plugin is
https://github.com/editorconfig/editorconfig-vim.git)
### Install with [Vundle][]
Use Vundle by adding to your `.vimrc` Vundle plugins section:
```viml
Plugin 'editorconfig/editorconfig-vim'
```
Then call `:PluginInstall`.
### Install with [vim-plug][]
Use vim-plug by adding to your `.vimrc` in your plugin section:
```viml
Plug 'editorconfig/editorconfig-vim'
```
Source your `.vimrc` by calling `:source $MYVIMRC`.
Then call `:PlugInstall`.
### No external editorconfig core library is required
Previous versions of this plugin also required a Python "core".
The core included the code to parse `.editorconfig` files.
This plugin **includes** the core, so you don't need to download the
core separately.
## Supported properties
The EditorConfig Vim plugin supports the following EditorConfig [properties][]:
* `indent_style`
* `indent_size`
* `tab_width`
* `end_of_line`
* `charset`
* `insert_final_newline` (Feature `+fixendofline`, available on Vim 7.4.785+,
or [PreserveNoEOL][] is required for this property)
* `trim_trailing_whitespace`
* `max_line_length`
* `root` (only used by EditorConfig core)
## Selected Options
The supported options are documented in [editorconfig.txt][]
and can be viewed by executing the following: `:help editorconfig`. You may
need to execute `:helptags ALL` so that Vim is aware of editorconfig.txt.
### Excluded patterns
To ensure that this plugin works well with [Tim Pope's fugitive][], use the
following patterns array:
```viml
let g:EditorConfig_exclude_patterns = ['fugitive://.*']
```
If you wanted to avoid loading EditorConfig for any remote files over ssh:
```viml
let g:EditorConfig_exclude_patterns = ['scp://.*']
```
Of course these two items could be combined into the following:
```viml
let g:EditorConfig_exclude_patterns = ['fugitive://.*', 'scp://.*']
```
### Disable for a specific filetype
You can disable this plugin for a specific buffer by setting
`b:EditorConfig_disable`. Therefore, you can disable the
plugin for all buffers of a specific filetype. For example, to disable
EditorConfig for all git commit messages (filetype `gitcommit`):
```viml
au FileType gitcommit let b:EditorConfig_disable = 1
```
### Disable rules
In very rare cases,
you might need to override some project-specific EditorConfig rules in global
or local vimrc in some cases, e.g., to resolve conflicts of trailing whitespace
trimming and buffer autosaving. This is not recommended, but you can:
```viml
let g:EditorConfig_disable_rules = ['trim_trailing_whitespace']
```
You are able to disable any supported EditorConfig properties.
## Bugs and Feature Requests
Feel free to submit bugs, feature requests, and other issues to the
[issue tracker][]. Be sure you have read the [contribution guidelines][]!
[EditorConfig]: http://editorconfig.org
[GitHub]: https://github.com/editorconfig/editorconfig-vim
[PreserveNoEOL]: http://www.vim.org/scripts/script.php?script_id=4550
[Tim Pope's fugitive]: https://github.com/tpope/vim-fugitive
[Vim online]: http://www.vim.org/scripts/script.php?script_id=3934
[Vundle]: https://github.com/gmarik/Vundle.vim
[archive]: https://github.com/editorconfig/editorconfig-vim/archive/master.zip
[contribution guidelines]: https://github.com/editorconfig/editorconfig/blob/master/CONTRIBUTING.md#submitting-an-issue
[issue tracker]: https://github.com/editorconfig/editorconfig-vim/issues
[pathogen]: https://github.com/tpope/vim-pathogen
[properties]: http://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties
[editorconfig.txt]: https://github.com/editorconfig/editorconfig-vim/blob/master/doc/editorconfig.txt
[vim-plug]: https://github.com/junegunn/vim-plug

View File

@@ -0,0 +1,60 @@
" autoload/editorconfig.vim: EditorConfig native Vim script plugin
" Copyright (c) 2011-2019 EditorConfig Team
" All rights reserved.
"
" Redistribution and use in source and binary forms, with or without
" modification, are permitted provided that the following conditions are met:
"
" 1. Redistributions of source code must retain the above copyright notice,
" this list of conditions and the following disclaimer.
" 2. Redistributions in binary form must reproduce the above copyright notice,
" this list of conditions and the following disclaimer in the documentation
" and/or other materials provided with the distribution.
"
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
" POSSIBILITY OF SUCH DAMAGE.
"
if v:version < 700
finish
endif
let s:saved_cpo = &cpo
set cpo&vim
" {{{1 variables
let s:hook_list = []
function! editorconfig#AddNewHook(func) " {{{1
" Add a new hook
call add(s:hook_list, a:func)
endfunction
function! editorconfig#ApplyHooks(config) abort " {{{1
" apply hooks
for Hook in s:hook_list
let l:hook_ret = Hook(a:config)
if type(l:hook_ret) != type(0) && l:hook_ret != 0
" TODO print some debug info here
endif
endfor
endfunction
" }}}
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vim: fdm=marker fdc=3

View File

@@ -0,0 +1,147 @@
" autoload/editorconfig_core.vim: top-level functions for
" editorconfig-core-vimscript and editorconfig-vim.
" Copyright (c) 2018-2020 EditorConfig Team, including Chris White {{{1
" All rights reserved.
"
" Redistribution and use in source and binary forms, with or without
" modification, are permitted provided that the following conditions are met:
"
" 1. Redistributions of source code must retain the above copyright notice,
" this list of conditions and the following disclaimer.
" 2. Redistributions in binary form must reproduce the above copyright notice,
" this list of conditions and the following disclaimer in the documentation
" and/or other materials provided with the distribution.
"
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
" POSSIBILITY OF SUCH DAMAGE. }}}1
let s:saved_cpo = &cpo
set cpo&vim
" Variables {{{1
" Note: we create this variable in every script that accesses it. Normally, I
" would put this in plugin/editorconfig.vim. However, in some of my tests,
" the command-line testing environment did not load plugin/* in the normal
" way. Therefore, I do the check everywhere so I don't have to special-case
" the command line.
if !exists('g:editorconfig_core_vimscript_debug')
let g:editorconfig_core_vimscript_debug = 0
endif
" }}}1
" The latest version of the specification that we support.
" See discussion at https://github.com/editorconfig/editorconfig/issues/395
function! editorconfig_core#version()
return [0,13,0]
endfunction
" === CLI =============================================================== {{{1
" For use from the command line. Output settings for in_name to
" the buffer named out_name. If an optional argument is provided, it is the
" name of the config file to use (default '.editorconfig').
" TODO support multiple files
"
" filename (if any)
" @param names {Dictionary} The names of the files to use for this run
" - output [required] Where the editorconfig settings should be written
" - target [required] A string or list of strings to process. Each
" must be a full path.
" - dump [optional] If present, write debug info to this file
" @param job {Dictionary} What to do - same format as the input of
" editorconfig_core#handler#get_configurations(),
" except without the target member.
function! editorconfig_core#currbuf_cli(names, job) " out_name, in_name, ...
let l:output = []
" Preprocess the job
let l:job = deepcopy(a:job)
if has_key(l:job, 'version') " string to list
let l:ver = split(editorconfig_core#util#strip(l:job.version), '\v\.')
for l:idx in range(len(l:ver))
let l:ver[l:idx] = str2nr(l:ver[l:idx])
endfor
let l:job.version = l:ver
endif
" TODO provide version output from here instead of the shell script
" if string(a:names) ==? 'version'
" return
" endif
"
if type(a:names) != type({}) || type(a:job) != type({})
throw 'Need two Dictionary arguments'
endif
if has_key(a:names, 'dump')
execute 'redir! > ' . fnameescape(a:names.dump)
echom 'Names: ' . string(a:names)
echom 'Job: ' . string(l:job)
let g:editorconfig_core_vimscript_debug = 1
endif
if type(a:names['target']) == type([])
let l:targets = a:names.target
else
let l:targets = [a:names.target]
endif
for l:target in l:targets
" Pre-process quoting weirdness so we are more flexible in the face
" of CMake+CTest+BAT+Powershell quoting.
" Permit wrapping in double-quotes
let l:target = substitute(l:target, '\v^"(.*)"$', '\1', '')
" Permit empty ('') entries in l:targets
if strlen(l:target)<1
continue
endif
if has_key(a:names, 'dump')
echom 'Trying: ' . string(l:target)
endif
let l:job.target = l:target
let l:options = editorconfig_core#handler#get_configurations(l:job)
if has_key(a:names, 'dump')
echom 'editorconfig_core#currbuf_cli result: ' . string(l:options)
endif
if len(l:targets) > 1
let l:output += [ '[' . l:target . ']' ]
endif
for [ l:key, l:value ] in items(l:options)
let l:output += [ l:key . '=' . l:value ]
endfor
endfor "foreach target
" Write the output file
call writefile(l:output, a:names.output)
endfunction "editorconfig_core#currbuf_cli
" }}}1
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vi: set fdm=marker fo-=ro:

View File

@@ -0,0 +1,467 @@
" autoload/editorconfig_core/fnmatch.vim: Globbing for
" editorconfig-vim. Ported from the Python core's fnmatch.py.
" Copyright (c) 2012-2019 EditorConfig Team {{{1
" All rights reserved.
"
" Redistribution and use in source and binary forms, with or without
" modification, are permitted provided that the following conditions are met:
"
" 1. Redistributions of source code must retain the above copyright notice,
" this list of conditions and the following disclaimer.
" 2. Redistributions in binary form must reproduce the above copyright notice,
" this list of conditions and the following disclaimer in the documentation
" and/or other materials provided with the distribution.
"
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
" POSSIBILITY OF SUCH DAMAGE. }}}1
"Filename matching with shell patterns.
"
"fnmatch(FILENAME, PATH, PATTERN) matches according to the local convention.
"fnmatchcase(FILENAME, PATH, PATTERN) always takes case in account.
"
"The functions operate by translating the pattern into a regular
"expression. They cache the compiled regular expressions for speed.
"
"The function translate(PATTERN) returns a regular expression
"corresponding to PATTERN. (It does not compile it.)
let s:saved_cpo = &cpo
set cpo&vim
" variables {{{1
if !exists('g:editorconfig_core_vimscript_debug')
let g:editorconfig_core_vimscript_debug = 0
endif
" }}}1
" === Regexes =========================================================== {{{1
let s:LEFT_BRACE = '\v[\\]@8<!\{'
" 8 is an arbitrary byte-count limit to the lookbehind (micro-optimization)
"LEFT_BRACE = re.compile(
" r"""
"
" (?<! \\ ) # Not preceded by "\"
"
" \{ # "{"
"
" """, re.VERBOSE
")
let s:RIGHT_BRACE = '\v[\\]@8<!\}'
" 8 is an arbitrary byte-count limit to the lookbehind (micro-optimization)
"RIGHT_BRACE = re.compile(
" r"""
"
" (?<! \\ ) # Not preceded by "\"
"
" \} # "}"
"
" """, re.VERBOSE
")
let s:NUMERIC_RANGE = '\v([+-]?\d+)' . '\.\.' . '([+-]?\d+)'
"NUMERIC_RANGE = re.compile(
" r"""
" ( # Capture a number
" [+-] ? # Zero or one "+" or "-" characters
" \d + # One or more digits
" )
"
" \.\. # ".."
"
" ( # Capture a number
" [+-] ? # Zero or one "+" or "-" characters
" \d + # One or more digits
" )
" """, re.VERBOSE
")
" }}}1
" === Internal functions ================================================ {{{1
" Dump the bytes of a:text. For debugging use.
function! s:dump_bytes(text)
let l:idx=0
while l:idx < strlen(a:text)
let l:byte_val = char2nr(a:text[l:idx])
echom printf('%10s%-5d%02x %s', '', l:idx, l:byte_val,
\ a:text[l:idx])
let l:idx+=1
endwhile
endfunction "s:dump_bytes
" Dump the characters of a:text and their codepoints. For debugging use.
function! s:dump_chars(text)
let l:chars = split(a:text, '\zs')
let l:idx = 0
let l:out1 = ''
let l:out2 = ''
while l:idx < len(l:chars)
let l:char = l:chars[l:idx]
let l:out1 .= printf('%5s', l:char)
let l:out2 .= printf('%5x', char2nr(l:char))
let l:idx+=1
endwhile
echom l:out1
echom l:out2
endfunction "s:dump_chars
" }}}1
" === Translating globs to patterns ===================================== {{{1
" Used by s:re_escape: backslash-escape any character below U+0080;
" replace all others with a %U escape.
" See https://vi.stackexchange.com/a/19617/1430 by yours truly
" (https://vi.stackexchange.com/users/1430/cxw).
unlockvar s:replacement_expr
let s:replacement_expr =
\ '\=' .
\ '((char2nr(submatch(1)) >= 128) ? ' .
\ 'printf("%%U%08x", char2nr(submatch(1))) : ' .
\ '("\\" . submatch(1))' .
\ ')'
lockvar s:replacement_expr
" Escaper for very-magic regexes
function! s:re_escape(text)
return substitute(a:text, '\v([^0-9a-zA-Z_])', s:replacement_expr, 'g')
endfunction
"def translate(pat, nested=0):
" Translate a shell PATTERN to a regular expression.
" There is no way to quote meta-characters.
function! editorconfig_core#fnmatch#translate(pat, ...)
let l:nested = 0
if a:0
let l:nested = a:1
endif
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#translate: pattern ' . a:pat
echom printf(
\ '- %d chars', strlen(substitute(a:pat, ".", "x", "g")))
call s:dump_chars(a:pat)
endif
let l:pat = a:pat " TODO remove if we wind up not needing this
" Note: the Python sets MULTILINE and DOTALL, but Vim has \_.
" instead of DOTALL, and \_^ / \_$ instead of MULTILINE.
let l:is_escaped = 0
" Find out whether the pattern has balanced braces.
let l:left_braces=[]
let l:right_braces=[]
call substitute(l:pat, s:LEFT_BRACE, '\=add(l:left_braces, 1)', 'g')
call substitute(l:pat, s:RIGHT_BRACE, '\=add(l:right_braces, 1)', 'g')
" Thanks to http://jeromebelleman.gitlab.io/posts/productivity/vimsub/
let l:matching_braces = (len(l:left_braces) == len(l:right_braces))
" Unicode support (#2). Indexing l:pat[l:index] returns bytes, per
" https://github.com/neovim/neovim/issues/68#issue-28114985 .
" Instead, use split() per vimdoc to break the input string into an
" array of *characters*, and process that.
let l:characters = split(l:pat, '\zs')
let l:index = 0 " character index
let l:length = len(l:characters)
let l:brace_level = 0
let l:in_brackets = 0
let l:result = ''
let l:numeric_groups = []
while l:index < l:length
let l:current_char = l:characters[l:index]
let l:index += 1
" if g:editorconfig_core_vimscript_debug
" echom ' - fnmatch#translate: ' . l:current_char . '@' .
" \ (l:index-1) . '; result ' . l:result
" endif
if l:current_char ==# '*'
let l:pos = l:index
if l:pos < l:length && l:characters[l:pos] ==# '*'
let l:result .= '\_.*'
let l:index += 1 " skip the second star
else
let l:result .= '[^/]*'
endif
elseif l:current_char ==# '?'
let l:result .= '\_[^/]'
elseif l:current_char ==# '['
if l:in_brackets
let l:result .= '\['
else
let l:pos = l:index
let l:has_slash = 0
while l:pos < l:length && l:characters[l:pos] != ']'
if l:characters[l:pos] ==# '/' && l:characters[l:pos-1] !=# '\'
let has_slash = 1
break
endif
let l:pos += 1
endwhile
if l:has_slash
" POSIX IEEE 1003.1-2017 sec. 2.13.3: '/' cannot occur
" in a bracket expression, so [/] matches a literal
" three-character string '[' . '/' . ']'.
let l:result .= '\['
\ . s:re_escape(join(l:characters[l:index : l:pos-1], ''))
\ . '\/'
" escape the slash
let l:index = l:pos + 1
" resume after the slash
else
if l:index < l:length && l:characters[l:index] =~# '\v%(\^|\!)'
let l:index += 1
let l:result .= '[^'
else
let l:result .= '['
endif
let l:in_brackets = 1
endif
endif
elseif l:current_char ==# '-'
if l:in_brackets
let l:result .= l:current_char
else
let l:result .= '\' . l:current_char
endif
elseif l:current_char ==# ']'
if l:in_brackets && !l:is_escaped
let l:result .= ']'
let l:in_brackets = 0
elseif l:is_escaped
let l:result .= '\]'
let l:is_escaped = 0
else
let l:result .= '\]'
endif
elseif l:current_char ==# '{'
let l:pos = l:index
let l:has_comma = 0
while l:pos < l:length && (l:characters[l:pos] !=# '}' || l:is_escaped)
if l:characters[l:pos] ==# ',' && ! l:is_escaped
let l:has_comma = 1
break
endif
let l:is_escaped = l:characters[l:pos] ==# '\' && ! l:is_escaped
let l:pos += 1
endwhile
if ! l:has_comma && l:pos < l:length
let l:num_range =
\ matchlist(join(l:characters[l:index : l:pos-1], ''),
\ s:NUMERIC_RANGE)
if len(l:num_range) > 0 " Remember the ranges
call add(l:numeric_groups, [ 0+l:num_range[1], 0+l:num_range[2] ])
let l:result .= '([+-]?\d+)'
else
let l:inner_xlat = editorconfig_core#fnmatch#translate(
\ join(l:characters[l:index : l:pos-1], ''), 1)
let l:inner_result = l:inner_xlat[0]
let l:inner_groups = l:inner_xlat[1]
let l:result .= '\{' . l:inner_result . '\}'
let l:numeric_groups += l:inner_groups
endif
let l:index = l:pos + 1
elseif l:matching_braces
let l:result .= '%('
let l:brace_level += 1
else
let l:result .= '\{'
endif
elseif l:current_char ==# ','
if l:brace_level > 0 && ! l:is_escaped
let l:result .= '|'
else
let l:result .= '\,'
endif
elseif l:current_char ==# '}'
if l:brace_level > 0 && ! l:is_escaped
let l:result .= ')'
let l:brace_level -= 1
else
let l:result .= '\}'
endif
elseif l:current_char ==# '/'
if join(l:characters[l:index : (l:index + 2)], '') ==# '**/'
let l:result .= '%(/|/\_.*/)'
let l:index += 3
else
let l:result .= '\/'
endif
elseif l:current_char != '\'
let l:result .= s:re_escape(l:current_char)
endif
if l:current_char ==# '\'
if l:is_escaped
let l:result .= s:re_escape(l:current_char)
endif
let l:is_escaped = ! l:is_escaped
else
let l:is_escaped = 0
endif
endwhile
if ! l:nested
let l:result .= '\_$'
endif
return [l:result, l:numeric_groups]
endfunction " #editorconfig_core#fnmatch#translate
let s:_cache = {}
function! s:cached_translate(pat)
if ! has_key(s:_cache, a:pat)
"regex = re.compile(res)
let s:_cache[a:pat] =
\ editorconfig_core#fnmatch#translate(a:pat)
" we don't compile the regex
endif
return s:_cache[a:pat]
endfunction " cached_translate
" }}}1
" === Matching functions ================================================ {{{1
function! editorconfig_core#fnmatch#fnmatch(name, path, pattern)
"def fnmatch(name, pat):
" """Test whether FILENAME matches PATH/PATTERN.
"
" Patterns are Unix shell style:
"
" - ``*`` matches everything except path separator
" - ``**`` matches everything
" - ``?`` matches any single character
" - ``[seq]`` matches any character in seq
" - ``[!seq]`` matches any char not in seq
" - ``{s1,s2,s3}`` matches any of the strings given (separated by commas)
"
" An initial period in FILENAME is not special.
" Both FILENAME and PATTERN are first case-normalized
" if the operating system requires it.
" If you don't want this, use fnmatchcase(FILENAME, PATTERN).
" """
"
" Note: This throws away the backslash in '\.txt' on Cygwin, but that
" makes sense since it's Windows under the hood.
" We don't care about shellslash since we're going to change backslashes
" to slashes in just a moment anyway.
let l:localname = fnamemodify(a:name, ':p')
if editorconfig_core#util#is_win() " normalize
let l:localname = substitute(tolower(l:localname), '\v\\', '/', 'g')
let l:path = substitute(tolower(a:path), '\v\\', '/', 'g')
let l:pattern = tolower(a:pattern)
else
let l:localname = l:localname
let l:path = a:path
let l:pattern = a:pattern
endif
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#fnmatch testing <' . l:localname . '> against <' .
\ l:pattern . '> wrt <' . l:path . '>'
endif
return editorconfig_core#fnmatch#fnmatchcase(l:localname, l:path, l:pattern)
endfunction " fnmatch
function! editorconfig_core#fnmatch#fnmatchcase(name, path, pattern)
"def fnmatchcase(name, pat):
" """Test whether FILENAME matches PATH/PATTERN, including case.
"
" This is a version of fnmatch() which doesn't case-normalize
" its arguments.
" """
"
let [regex, num_groups] = s:cached_translate(a:pattern)
let l:escaped_path = s:re_escape(a:path)
let l:regex = '\v' . l:escaped_path . l:regex
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#fnmatchcase: regex ' . l:regex
call s:dump_chars(l:regex)
echom '- fnmatch#fnmatchcase: checking ' . a:name
call s:dump_chars(a:name)
endif
let l:match_groups = matchlist(a:name, l:regex)[1:] " [0] = full match
if g:editorconfig_core_vimscript_debug
echom printf(' Got %d matches', len(l:match_groups))
endif
if len(l:match_groups) == 0
return 0
endif
" Check numeric ranges
let pattern_matched = 1
for l:idx in range(0,len(l:match_groups))
let l:num = l:match_groups[l:idx]
if l:num ==# ''
break
endif
let [min_num, max_num] = num_groups[l:idx]
if (min_num > (0+l:num)) || ((0+l:num) > max_num)
let pattern_matched = 0
break
endif
" Reject leading zeros without sign. This is very odd ---
" see editorconfig/editorconfig#371.
if match(l:num, '\v^0') != -1
let pattern_matched = 0
break
endif
endfor
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#fnmatchcase: ' . (pattern_matched ? 'matched' : 'did not match')
endif
return pattern_matched
endfunction " fnmatchcase
" }}}1
" === Copyright notices ================================================= {{{1
" Based on code from fnmatch.py file distributed with Python 2.6.
" Portions Copyright (c) 2001-2010 Python Software Foundation;
" All Rights Reserved. Licensed under PSF License (see LICENSE.PSF file).
"
" Changes to original fnmatch:
"
" - translate function supports ``*`` and ``**`` similarly to fnmatch C library
" }}}1
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vi: set fdm=marker:

View File

@@ -0,0 +1,183 @@
" autoload/editorconfig_core/handler.vim: Main worker for
" editorconfig-core-vimscript and editorconfig-vim.
" Modified from the Python core's handler.py.
" Copyright (c) 2012-2019 EditorConfig Team {{{1
" All rights reserved.
"
" Redistribution and use in source and binary forms, with or without
" modification, are permitted provided that the following conditions are met:
"
" 1. Redistributions of source code must retain the above copyright notice,
" this list of conditions and the following disclaimer.
" 2. Redistributions in binary form must reproduce the above copyright notice,
" this list of conditions and the following disclaimer in the documentation
" and/or other materials provided with the distribution.
"
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
" POSSIBILITY OF SUCH DAMAGE. }}}1
let s:saved_cpo = &cpo
set cpo&vim
" Return full filepath for filename in each directory in and above path. {{{1
" Input path must be an absolute path.
" TODO shellslash/shellescape?
function! s:get_filenames(path, config_filename)
let l:path = a:path
let l:path_list = []
while 1
call add(l:path_list, editorconfig_core#util#path_join(l:path, a:config_filename))
let l:newpath = fnamemodify(l:path, ':h')
if l:path ==? l:newpath || !strlen(l:path)
break
endif
let l:path = l:newpath
endwhile
return l:path_list
endfunction " get_filenames
" }}}1
" === Main ============================================================== {{{1
" Find EditorConfig files and return all options matching target_filename.
" Throws on failure.
" @param job {Dictionary} required 'target'; optional 'config' and 'version'
function! editorconfig_core#handler#get_configurations(job)
" TODO? support VERSION checks?
" Special exceptions that may be raised by this function include:
" - ``VersionError``: self.version is invalid EditorConfig version
" - ``PathError``: self.filepath is not a valid absolute filepath
" - ``ParsingError``: improperly formatted EditorConfig file found
let l:job = deepcopy(a:job)
if has_key(l:job, 'config')
let l:config_filename = l:job.config
else
let l:config_filename = '.editorconfig'
let l:job.config = l:config_filename
endif
if has_key(l:job, 'version')
let l:version = l:job.version
else
let l:version = editorconfig_core#version()
let l:job.version = l:version
endif
let l:target_filename = l:job.target
"echom 'Beginning job ' . string(l:job)
if !s:check_assertions(l:job)
throw "Assertions failed"
endif
let l:fullpath = fnamemodify(l:target_filename,':p')
let l:path = fnamemodify(l:fullpath, ':h')
let l:conf_files = s:get_filenames(l:path, l:config_filename)
" echom 'fullpath ' . l:fullpath
" echom 'path ' . l:path
let l:retval = {}
" Attempt to find and parse every EditorConfig file in filetree
for l:conf_fn in l:conf_files
"echom 'Trying ' . l:conf_fn
let l:parsed = editorconfig_core#ini#read_ini_file(l:conf_fn, l:target_filename)
if !has_key(l:parsed, 'options')
continue
endif
" echom ' Has options'
" Merge new EditorConfig file's options into current options
let l:old_options = l:retval
let l:retval = l:parsed.options
" echom 'Old options ' . string(l:old_options)
" echom 'New options ' . string(l:retval)
call extend(l:retval, l:old_options, 'force')
" Stop parsing if parsed file has a ``root = true`` option
if l:parsed.root
break
endif
endfor
call s:preprocess_values(l:job, l:retval)
return l:retval
endfunction " get_configurations
function! s:check_assertions(job)
" TODO
" """Raise error if filepath or version have invalid values"""
" # Raise ``PathError`` if filepath isn't an absolute path
" if not os.path.isabs(self.filepath):
" raise PathError("Input file must be a full path name.")
" Throw if version specified is greater than current
let l:v = a:job.version
let l:us = editorconfig_core#version()
" echom 'Comparing requested version ' . string(l:v) .
" \ ' to our version ' . string(l:us)
if l:v[0] > l:us[0] || l:v[1] > l:us[1] || l:v[2] > l:us[2]
throw 'Required version ' . string(l:v) .
\ ' is greater than the current version ' . string(l:us)
endif
return 1 " All OK if we got here
endfunction " check_assertions
" }}}1
" Preprocess option values for consumption by plugins. {{{1
" Modifies its argument in place.
function! s:preprocess_values(job, opts)
" Lowercase option value for certain options
for l:name in ['end_of_line', 'indent_style', 'indent_size',
\ 'insert_final_newline', 'trim_trailing_whitespace',
\ 'charset']
if has_key(a:opts, l:name)
let a:opts[l:name] = tolower(a:opts[l:name])
endif
endfor
" Set indent_size to "tab" if indent_size is unspecified and
" indent_style is set to "tab", provided we are at least v0.10.0.
if get(a:opts, 'indent_style', '') ==? "tab" &&
\ !has_key(a:opts, 'indent_size') &&
\ ( a:job.version[0]>0 || a:job.version[1] >=10 )
let a:opts['indent_size'] = 'tab'
endif
" Set tab_width to indent_size if indent_size is specified and
" tab_width is unspecified
if has_key(a:opts, 'indent_size') && !has_key(a:opts, 'tab_width') &&
\ get(a:opts, 'indent_size', '') !=? "tab"
let a:opts['tab_width'] = a:opts['indent_size']
endif
" Set indent_size to tab_width if indent_size is "tab"
if has_key(a:opts, 'indent_size') && has_key(a:opts, 'tab_width') &&
\ get(a:opts, 'indent_size', '') ==? "tab"
let a:opts['indent_size'] = a:opts['tab_width']
endif
endfunction " preprocess_values
" }}}1
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vi: set fdm=marker fdl=1:

View File

@@ -0,0 +1,264 @@
" autoload/editorconfig_core/ini.vim: Config-file parser for
" editorconfig-core-vimscript and editorconfig-vim.
" Modified from the Python core's ini.py.
" Copyright (c) 2012-2019 EditorConfig Team {{{2
" All rights reserved.
"
" Redistribution and use in source and binary forms, with or without
" modification, are permitted provided that the following conditions are met:
"
" 1. Redistributions of source code must retain the above copyright notice,
" this list of conditions and the following disclaimer.
" 2. Redistributions in binary form must reproduce the above copyright notice,
" this list of conditions and the following disclaimer in the documentation
" and/or other materials provided with the distribution.
"
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
" POSSIBILITY OF SUCH DAMAGE. }}}2
let s:saved_cpo = &cpo
set cpo&vim
" variables {{{2
if !exists('g:editorconfig_core_vimscript_debug')
let g:editorconfig_core_vimscript_debug = 0
endif
" }}}2
" === Constants, including regexes ====================================== {{{2
" Regular expressions for parsing section headers and options.
" Allow ``]`` and escaped ``;`` and ``#`` characters in section headers.
" In fact, allow \ to escape any single character - it needs to cover at
" least \ * ? [ ! ] { }.
unlockvar s:SECTCRE s:OPTCRE s:MAX_SECTION_NAME s:MAX_PROPERTY_NAME s:MAX_PROPERTY_VALUE
let s:SECTCRE = '\v^\s*\[(%([^\\#;]|\\.)+)\]'
" Regular expression for parsing option name/values.
" Allow any amount of whitespaces, followed by separator
" (either ``:`` or ``=``), followed by any amount of whitespace and then
" any characters to eol
let s:OPTCRE = '\v\s*([^:=[:space:]][^:=]*)\s*([:=])\s*(.*)$'
let s:MAX_SECTION_NAME = 4096
let s:MAX_PROPERTY_NAME = 1024
let s:MAX_PROPERTY_VALUE = 4096
lockvar s:SECTCRE s:OPTCRE s:MAX_SECTION_NAME s:MAX_PROPERTY_NAME s:MAX_PROPERTY_VALUE
" }}}2
" === Main ============================================================== {{{1
" Read \p config_filename and return the options applicable to
" \p target_filename. This is the main entry point in this file.
function! editorconfig_core#ini#read_ini_file(config_filename, target_filename)
if !filereadable(a:config_filename)
return {}
endif
try
let l:lines = readfile(a:config_filename)
if &encoding !=? 'utf-8'
" strip BOM
if len(l:lines) > 0 && l:lines[0][:2] ==# "\xEF\xBB\xBF"
let l:lines[0] = l:lines[0][3:]
endif
" convert from UTF-8 to 'encoding'
call map(l:lines, 'iconv(v:val, "utf-8", &encoding)')
endif
let result = s:parse(a:config_filename, a:target_filename, l:lines)
catch
" rethrow, but with a prefix since throw 'Vim...' fails.
throw 'Could not read editorconfig file at ' . v:throwpoint . ': ' . string(v:exception)
endtry
return result
endfunction
function! s:parse(config_filename, target_filename, lines)
" Parse a sectioned setup file.
" The sections in setup file contains a title line at the top,
" indicated by a name in square brackets (`[]'), plus key/value
" options lines, indicated by `name: value' format lines.
" Continuations are represented by an embedded newline then
" leading whitespace. Blank lines, lines beginning with a '#',
" and just about everything else are ignored.
let l:in_section = 0
let l:matching_section = 0
let l:optname = ''
let l:lineno = 0
let l:e = [] " Errors, if any
let l:options = {} " Options applicable to this file
let l:is_root = 0 " Whether a:config_filename declares root=true
while 1
if l:lineno == len(a:lines)
break
endif
let l:line = a:lines[l:lineno]
let l:lineno = l:lineno + 1
" comment or blank line?
if editorconfig_core#util#strip(l:line) ==# ''
continue
endif
if l:line =~# '\v^[#;]'
continue
endif
" is it a section header?
if g:editorconfig_core_vimscript_debug
echom "Header? <" . l:line . ">"
endif
let l:mo = matchlist(l:line, s:SECTCRE)
if len(l:mo)
let l:sectname = l:mo[1]
let l:in_section = 1
if strlen(l:sectname) > s:MAX_SECTION_NAME
" Section name too long => ignore the section
let l:matching_section = 0
else
let l:matching_section = s:matches_filename(
\ a:config_filename, a:target_filename, l:sectname)
endif
if g:editorconfig_core_vimscript_debug
echom 'In section ' . l:sectname . ', which ' .
\ (l:matching_section ? 'matches' : 'does not match')
\ ' file ' . a:target_filename . ' (config ' .
\ a:config_filename . ')'
endif
" So sections can't start with a continuation line
let l:optname = ''
" Is it an option line?
else
let l:mo = matchlist(l:line, s:OPTCRE)
if len(l:mo)
let l:optname = mo[1]
let l:optval = mo[3]
if g:editorconfig_core_vimscript_debug
echom printf('Saw raw opt <%s>=<%s>', l:optname, l:optval)
endif
let l:optval = editorconfig_core#util#strip(l:optval)
" allow empty values
if l:optval ==? '""'
let l:optval = ''
endif
let l:optname = s:optionxform(l:optname)
if !l:in_section && optname ==? 'root'
let l:is_root = (optval ==? 'true')
endif
if g:editorconfig_core_vimscript_debug
echom printf('Saw opt <%s>=<%s>', l:optname, l:optval)
endif
if l:matching_section &&
\ strlen(l:optname) <= s:MAX_PROPERTY_NAME &&
\ strlen(l:optval) <= s:MAX_PROPERTY_VALUE
let l:options[l:optname] = l:optval
endif
else
" a non-fatal parsing error occurred. set up the
" exception but keep going. the exception will be
" raised at the end of the file and will contain a
" list of all bogus lines
call add(e, "Parse error in '" . a:config_filename . "' at line " .
\ l:lineno . ": '" . l:line . "'")
endif
endif
endwhile
" if any parsing errors occurred, raise an exception
if len(l:e)
throw string(l:e)
endif
return {'root': l:is_root, 'options': l:options}
endfunction!
" }}}1
" === Helpers =========================================================== {{{1
" Preprocess option names
function! s:optionxform(optionstr)
let l:result = substitute(a:optionstr, '\v\s+$', '', 'g') " rstrip
return tolower(l:result)
endfunction
" Return true if \p glob matches \p target_filename
function! s:matches_filename(config_filename, target_filename, glob)
" config_dirname = normpath(dirname(config_filename)).replace(sep, '/')
let l:config_dirname = fnamemodify(a:config_filename, ':p:h') . '/'
if editorconfig_core#util#is_win()
" Regardless of whether shellslash is set, make everything slashes
let l:config_dirname =
\ tolower(substitute(l:config_dirname, '\v\\', '/', 'g'))
endif
let l:glob = substitute(a:glob, '\v\\([#;])', '\1', 'g')
" Take account of the path to the editorconfig file.
" editorconfig-core-c/src/lib/editorconfig.c says:
" "Pattern would be: /dir/of/editorconfig/file[double_star]/[section] if
" section does not contain '/', or /dir/of/editorconfig/file[section]
" if section starts with a '/', or /dir/of/editorconfig/file/[section] if
" section contains '/' but does not start with '/'."
if stridx(l:glob, '/') != -1 " contains a slash
if l:glob[0] ==# '/'
let l:glob = l:glob[1:] " trim leading slash
endif
" This will be done by fnmatch
" let l:glob = l:config_dirname . l:glob
else " does not contain a slash
let l:config_dirname = l:config_dirname[:-2]
" Trim trailing slash
let l:glob = '**/' . l:glob
endif
if g:editorconfig_core_vimscript_debug
echom '- ini#matches_filename: checking <' . a:target_filename .
\ '> against <' . l:glob . '> with respect to config file <' .
\ a:config_filename . '>'
echom '- ini#matches_filename: config_dirname is ' . l:config_dirname
endif
return editorconfig_core#fnmatch#fnmatch(a:target_filename,
\ l:config_dirname, l:glob)
endfunction " matches_filename
" }}}1
" === Copyright notices ================================================= {{{2
" Based on code from ConfigParser.py file distributed with Python 2.6.
" Portions Copyright (c) 2001-2010 Python Software Foundation;
" All Rights Reserved. Licensed under PSF License (see LICENSE.PSF file).
"
" Changes to original ConfigParser:
"
" - Special characters can be used in section names
" - Octothorpe can be used for comments (not just at beginning of line)
" - Only track INI options in sections that match target filename
" - Stop parsing files with when ``root = true`` is found
" }}}2
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vi: set fdm=marker fdl=1:

View File

@@ -0,0 +1,84 @@
" util.vim: part of editorconfig-core-vimscript and editorconfig-vim.
" Copyright (c) 2018-2019 EditorConfig Team, including Chris White {{{1
" All rights reserved.
"
" Redistribution and use in source and binary forms, with or without
" modification, are permitted provided that the following conditions are met:
"
" 1. Redistributions of source code must retain the above copyright notice,
" this list of conditions and the following disclaimer.
" 2. Redistributions in binary form must reproduce the above copyright notice,
" this list of conditions and the following disclaimer in the documentation
" and/or other materials provided with the distribution.
"
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
" POSSIBILITY OF SUCH DAMAGE. }}}1
let s:saved_cpo = &cpo
set cpo&vim
" A verbatim copy of ingo#fs#path#Separator() {{{1
" from https://github.com/vim-scripts/ingo-library/blob/558132e2221db3af26dc2f2c6756d092d48a459f/autoload/ingo/fs/path.vim
" distributed under the Vim license.
function! editorconfig_core#util#Separator()
return (exists('+shellslash') && ! &shellslash ? '\' : '/')
endfunction " }}}1
" path_join(): ('a','b')->'a/b'; ('a/','b')->'a/b'. {{{1
function! editorconfig_core#util#path_join(a, b)
" TODO shellescape/shellslash?
"echom 'Joining <' . a:a . '> and <' . a:b . '>'
"echom 'Length is ' . strlen(a:a)
"echom 'Last char is ' . char2nr(a:a[-1])
if a:a !~# '\v%(\/|\\)$'
return a:a . editorconfig_core#util#Separator() . a:b
else
return a:a . a:b
endif
endfunction " }}}1
" is_win() by xolox {{{1
" The following function is modified from
" https://github.com/xolox/vim-misc/blob/master/autoload/xolox/misc/os.vim
" Copyright (c) 2015 Peter Odding <peter@peterodding.com>
"
" Permission is hereby granted, free of charge, to any person obtaining a copy
" of this software and associated documentation files (the "Software"), to deal
" in the Software without restriction, including without limitation the rights
" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
" copies of the Software, and to permit persons to whom the Software is
" furnished to do so, subject to the following conditions:
"
" The above copyright notice and this permission notice shall be included in all
" copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
" SOFTWARE.
function! editorconfig_core#util#is_win()
" Returns 1 (true) when on Microsoft Windows, 0 (false) otherwise.
return has('win16') || has('win32') || has('win64')
endfunction " }}}1
" strip() {{{1
function! editorconfig_core#util#strip(s)
return substitute(a:s, '\v^\s+|\s+$','','g')
endfunction " }}}1
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vi: set fdm=marker:

View File

@@ -0,0 +1,238 @@
*editorconfig.txt* EditorConfig plugin for vim.
File: editorconfig.txt
Version: 1.1.1
Maintainer: EditorConfig Team <http://editorconfig.org>
Description: EditorConfig vim plugin
CONTENTS~
*editorconfig-contents*
----------------------------------------------------------------------------
1. Overview |editorconfig-overview|
2. Installation |editorconfig-installation|
3. Commands |editorconfig-commands|
4. Settings |editorconfig-settings|
5. Advanced |editorconfig-advanced|
6. License |editorconfig-license|
OVERVIEW~
*editorconfig-overview*
----------------------------------------------------------------------------
This is the EditorConfig plugin for vim.
INSTALLATION~
*editorconfig-installation*
----------------------------------------------------------------------------
Follow the instructions in the README.md file to install this plugin.
COMMANDS~
*editorconfig-commands*
----------------------------------------------------------------------------
*:EditorConfigReload*
Command:
:EditorConfigReload
Reload the EditorConfig conf files. When `.editorconfig` files are modified,
this command could prevent you to reload the current edited file to load the
new configuration.
SETTINGS~
*editorconfig-settings*
----------------------------------------------------------------------------
*g:EditorConfig_core_mode*
Specify the mode of EditorConfig core. Generally it is OK to leave this option
empty. Currently, the supported modes are "vim_core" (default) and
"external_command".
vim_core: Use the included Vim script EditorConfig Core.
external_command: Run external EditorConfig Core.
If "g:EditorConfig_core_mode" is not specified, this plugin will automatically
choose "vim_core".
If you choose "external_command" mode, you must also set
|g:EditorConfig_exec_path|.
Changes to "g:EditorConfig_core_mode" will not take effect until Vim
is restarted.
*b:EditorConfig_disable*
This is a buffer-local variable that disables the EditorConfig plugin for a
single buffer.
Example: Disable EditorConfig for the current buffer:
>
let b:EditorConfig_disable = 1
<
Example: Disable EditorConfig for all git commit messages:
>
au FileType gitcommit let b:EditorConfig_disable = 1
<
*g:EditorConfig_exclude_patterns*
This is a list contains file path patterns which will be ignored by
EditorConfig plugin. When the path of the opened buffer (i.e.
"expand('%:p')") matches any of the patterns in the list, EditorConfig will
not load for this file. The default is an empty list.
Example: Avoid loading EditorConfig for any remote files over ssh
>
let g:EditorConfig_exclude_patterns = ['scp://.*']
<
*g:EditorConfig_exec_path*
The file path to the EditorConfig core executable. You can set this value in
your |vimrc| like this:
>
let g:EditorConfig_exec_path = 'Path to your EditorConfig Core executable'
<
The default value is empty.
If "g:EditorConfig_exec_path" is not set, the plugin will use the "vim_core"
mode regardless of the setting of |g:EditorConfig_core_mode|.
Changes to "g:EditorConfig_exec_path" will not take effect until Vim
is restarted.
*g:EditorConfig_max_line_indicator*
The way to show the line where the maximal length is reached. Accepted values
are "line", "fill", "exceeding" and "fillexceeding", otherwise there will be
no max line indicator.
"line": the right column of the max line length column will be
highlighted on all lines, by adding +1 to 'colorcolumn'.
"fill": all the columns to the right of the max line length
column will be highlighted on all lines, by setting
'colorcolumn' to a list starting from "max_line_length +
1" to the number of columns on the screen.
"exceeding": the right column of the max line length column will be
highlighted on lines that exceed the max line length, by
adding a match for the ColorColumn group.
"fillexceeding": all the columns to the right of the max line length
column will be highlighted on lines that exceed the max
line length, by adding a match for the ColorColumn group.
"none": no max line length indicator will be shown. Recommended
when you do not want any indicator to be shown, but any
value other than those listed above also work as "none".
To set this option, add any of the following lines to your |vimrc| file:
>
let g:EditorConfig_max_line_indicator = "line"
let g:EditorConfig_max_line_indicator = "fill"
let g:EditorConfig_max_line_indicator = "exceeding"
let g:EditorConfig_max_line_indicator = "fillexceeding"
let g:EditorConfig_max_line_indicator = "none"
<
The default value is "line".
*g:EditorConfig_enable_for_new_buf*
Set this to 1 if you want EditorConfig plugin to set options
for new empty buffers too.
Path to .editorconfig will be determined based on CWD (see |getcwd()|)
>
let g:EditorConfig_enable_for_new_buf = 1
<
This option defaults to 0.
*g:EditorConfig_preserve_formatoptions*
Set this to 1 if you don't want your formatoptions modified when
max_line_length is set:
>
let g:EditorConfig_preserve_formatoptions = 1
<
This option defaults to 0.
*g:EditorConfig_softtabstop_space*
When spaces are used for indent, Vim's 'softtabstop' feature will make the
backspace key delete one indent level. If you turn off that feature (by
setting the option to 0), only a single space will be deleted.
This option defaults to 1, which enables 'softtabstop' and uses the
'shiftwidth' value for it. You can also set this to -1 to automatically follow
the current 'shiftwidth' value (since Vim 7.3.693). Or set this to [] if
EditorConfig should not touch 'softtabstop' at all.
*g:EditorConfig_softtabstop_tab*
When tabs are used for indent, Vim's 'softtabstop' feature only applies to
backspacing over existing runs of spaces.
This option defaults to 1, so backspace will delete one indent level worth of
spaces; -1 does the same but automatically follows the current 'shiftwidth'
value. Set this to 0 to have backspace delete just a single space character.
Or set this to [] if EditorConfig should not touch 'softtabstop' at all.
*g:EditorConfig_verbose*
Set this to 1 if you want debug info printed:
>
let g:EditorConfig_verbose = 1
<
ADVANCED~
*editorconfig-advanced*
----------------------------------------------------------------------------
*editorconfig-hook*
*EditorConfig#AddNewHook()*
While this plugin offers several builtin supported properties (as mentioned
here: https://github.com/editorconfig/editorconfig-vim#supported-properties),
we are also able to add our own hooks to support additional EditorConfig
properties, including those not in the EditorConfig standard. For example, we
are working on an Objective-C project, and all our "*.m" files should be
Objective-C source files. However, vim sometimes detect "*.m" files as MATLAB
source files, which causes incorrect syntax highlighting, code indentation,
etc. To solve the case, we could write the following code into the |vimrc|
file:
>
function! FiletypeHook(config)
if has_key(a:config, 'vim_filetype')
let &filetype = a:config['vim_filetype']
endif
return 0 " Return 0 to show no error happened
endfunction
call editorconfig#AddNewHook(function('FiletypeHook'))
<
And add the following code to your .editorconfig file:
>
[*.m]
vim_filetype = objc
<
Then try to open an Objective-C file, you will find the |filetype| is set to
"objc".
License~
*editorconfig-license*
----------------------------------------------------------------------------
License:
Copyright (c) 2011-2019 EditorConfig Team
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
vim:ft=help:tw=78:cc=

View File

@@ -0,0 +1,21 @@
:EditorConfigReload editorconfig.txt /*:EditorConfigReload*
EditorConfig#AddNewHook() editorconfig.txt /*EditorConfig#AddNewHook()*
b:EditorConfig_disable editorconfig.txt /*b:EditorConfig_disable*
editorconfig-advanced editorconfig.txt /*editorconfig-advanced*
editorconfig-commands editorconfig.txt /*editorconfig-commands*
editorconfig-contents editorconfig.txt /*editorconfig-contents*
editorconfig-hook editorconfig.txt /*editorconfig-hook*
editorconfig-installation editorconfig.txt /*editorconfig-installation*
editorconfig-license editorconfig.txt /*editorconfig-license*
editorconfig-overview editorconfig.txt /*editorconfig-overview*
editorconfig-settings editorconfig.txt /*editorconfig-settings*
editorconfig.txt editorconfig.txt /*editorconfig.txt*
g:EditorConfig_core_mode editorconfig.txt /*g:EditorConfig_core_mode*
g:EditorConfig_enable_for_new_buf editorconfig.txt /*g:EditorConfig_enable_for_new_buf*
g:EditorConfig_exclude_patterns editorconfig.txt /*g:EditorConfig_exclude_patterns*
g:EditorConfig_exec_path editorconfig.txt /*g:EditorConfig_exec_path*
g:EditorConfig_max_line_indicator editorconfig.txt /*g:EditorConfig_max_line_indicator*
g:EditorConfig_preserve_formatoptions editorconfig.txt /*g:EditorConfig_preserve_formatoptions*
g:EditorConfig_softtabstop_space editorconfig.txt /*g:EditorConfig_softtabstop_space*
g:EditorConfig_softtabstop_tab editorconfig.txt /*g:EditorConfig_softtabstop_tab*
g:EditorConfig_verbose editorconfig.txt /*g:EditorConfig_verbose*

View File

@@ -0,0 +1 @@
autocmd BufNewFile,BufRead .editorconfig setfiletype dosini

View File

@@ -0,0 +1,3 @@
#!/bin/sh
zip -r editorconfig-vim-$*.zip autoload/* doc/* ftdetect/* plugin/*

View File

@@ -0,0 +1,614 @@
" plugin/editorconfig.vim: EditorConfig native Vim script plugin file
" Copyright (c) 2011-2019 EditorConfig Team
" All rights reserved.
"
" Redistribution and use in source and binary forms, with or without
" modification, are permitted provided that the following conditions are met:
"
" 1. Redistributions of source code must retain the above copyright notice,
" this list of conditions and the following disclaimer.
" 2. Redistributions in binary form must reproduce the above copyright notice,
" this list of conditions and the following disclaimer in the documentation
" and/or other materials provided with the distribution.
"
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
" POSSIBILITY OF SUCH DAMAGE.
"
" check for Vim versions and duplicate script loading.
if v:version < 700 || exists("g:loaded_EditorConfig")
finish
endif
let g:loaded_EditorConfig = 1
let s:saved_cpo = &cpo
set cpo&vim
" variables {{{1
" Make sure the globals all exist
if !exists('g:EditorConfig_exec_path')
let g:EditorConfig_exec_path = ''
endif
if !exists('g:EditorConfig_verbose')
let g:EditorConfig_verbose = 0
endif
if !exists('g:EditorConfig_preserve_formatoptions')
let g:EditorConfig_preserve_formatoptions = 0
endif
if !exists('g:EditorConfig_max_line_indicator')
let g:EditorConfig_max_line_indicator = 'line'
endif
if !exists('g:EditorConfig_exclude_patterns')
let g:EditorConfig_exclude_patterns = []
endif
if !exists('g:EditorConfig_disable_rules')
let g:EditorConfig_disable_rules = []
endif
if !exists('g:EditorConfig_enable_for_new_buf')
let g:EditorConfig_enable_for_new_buf = 0
endif
if !exists('g:EditorConfig_softtabstop_space')
let g:EditorConfig_softtabstop_space = 1
endif
if !exists('g:EditorConfig_softtabstop_tab')
let g:EditorConfig_softtabstop_tab = 1
endif
" Copy some of the globals into script variables --- changes to these
" globals won't affect the plugin until the plugin is reloaded.
if exists('g:EditorConfig_core_mode') && !empty(g:EditorConfig_core_mode)
let s:editorconfig_core_mode = g:EditorConfig_core_mode
else
let s:editorconfig_core_mode = ''
endif
if exists('g:EditorConfig_exec_path') && !empty(g:EditorConfig_exec_path)
let s:editorconfig_exec_path = g:EditorConfig_exec_path
else
let s:editorconfig_exec_path = ''
endif
let s:initialized = 0
" }}}1
" shellslash handling {{{1
function! s:DisableShellSlash(bufnr) " {{{2
" disable shellslash for proper escaping of Windows paths
" In Windows, 'shellslash' also changes the behavior of 'shellescape'.
" It makes 'shellescape' behave like in UNIX environment. So ':setl
" noshellslash' before evaluating 'shellescape' and restore the
" settings afterwards when 'shell' does not contain 'sh' somewhere.
let l:shell = getbufvar(a:bufnr, '&shell')
if has('win32') && empty(matchstr(l:shell, 'sh'))
let s:old_shellslash = getbufvar(a:bufnr, '&shellslash')
setbufvar(a:bufnr, '&shellslash', 0)
endif
endfunction " }}}2
function! s:ResetShellSlash(bufnr) " {{{2
" reset shellslash to the user-set value, if any
if exists('s:old_shellslash')
setbufvar(a:bufnr, '&shellslash', s:old_shellslash)
unlet! s:old_shellslash
endif
endfunction " }}}2
" }}}1
" Mode initialization functions {{{1
function! s:InitializeVimCore()
" Initialize vim core. Returns 1 on failure; 0 on success
" At the moment, all we need to do is to check that it is installed.
try
let l:vim_core_ver = editorconfig_core#version()
catch
return 1
endtry
return 0
endfunction
function! s:InitializeExternalCommand()
" Initialize external_command mode
if empty(s:editorconfig_exec_path)
echo 'Please specify a g:EditorConfig_exec_path'
return 1
endif
if g:EditorConfig_verbose
echo 'Checking for external command ' . s:editorconfig_exec_path . ' ...'
endif
if !executable(s:editorconfig_exec_path)
echo 'File ' . s:editorconfig_exec_path . ' is not executable.'
return 1
endif
return 0
endfunction
" }}}1
function! s:Initialize() " Initialize the plugin. {{{1
" Returns truthy on error, falsy on success.
if empty(s:editorconfig_core_mode)
let s:editorconfig_core_mode = 'vim_core' " Default core choice
endif
if s:editorconfig_core_mode ==? 'external_command'
if s:InitializeExternalCommand()
echohl WarningMsg
echo 'EditorConfig: Failed to initialize external_command mode. ' .
\ 'Falling back to vim_core mode.'
echohl None
let s:editorconfig_core_mode = 'vim_core'
endif
endif
if s:editorconfig_core_mode ==? 'vim_core'
if s:InitializeVimCore()
echohl ErrorMsg
echo 'EditorConfig: Failed to initialize vim_core mode. ' .
\ 'The plugin will not function.'
echohl None
return 1
endif
elseif s:editorconfig_core_mode ==? 'external_command'
" Nothing to do here, but this elseif is required to avoid
" external_command falling into the else clause.
else " neither external_command nor vim_core
echohl ErrorMsg
echo "EditorConfig: I don't know how to use mode " . s:editorconfig_core_mode
echohl None
return 1
endif
let s:initialized = 1
return 0
endfunction " }}}1
function! s:GetFilenames(path, filename) " {{{1
" Yield full filepath for filename in each directory in and above path
let l:path_list = []
let l:path = a:path
while 1
let l:path_list += [l:path . '/' . a:filename]
let l:newpath = fnamemodify(l:path, ':h')
if l:path == l:newpath
break
endif
let l:path = l:newpath
endwhile
return l:path_list
endfunction " }}}1
function! s:UseConfigFiles(from_autocmd) abort " Apply config to the current buffer {{{1
" from_autocmd is truthy if called from an autocmd, falsy otherwise.
" Get the properties of the buffer we are working on
if a:from_autocmd
let l:bufnr = str2nr(expand('<abuf>'))
let l:buffer_name = expand('<afile>:p')
let l:buffer_path = expand('<afile>:p:h')
else
let l:bufnr = bufnr('%')
let l:buffer_name = expand('%:p')
let l:buffer_path = expand('%:p:h')
endif
call setbufvar(l:bufnr, 'editorconfig_tried', 1)
" Only process normal buffers (do not treat help files as '.txt' files)
" When starting Vim with a directory, the buftype might not yet be set:
" Therefore, also check if buffer_name is a directory.
if index(['', 'acwrite'], &buftype) == -1 || isdirectory(l:buffer_name)
return
endif
if empty(l:buffer_name)
if g:EditorConfig_enable_for_new_buf
let l:buffer_name = getcwd() . "/."
else
if g:EditorConfig_verbose
echo 'Skipping EditorConfig for unnamed buffer'
endif
return
endif
endif
if getbufvar(l:bufnr, 'EditorConfig_disable', 0)
if g:EditorConfig_verbose
echo 'EditorConfig disabled --- skipping buffer "' . l:buffer_name . '"'
endif
return
endif
" Ignore specific patterns
for pattern in g:EditorConfig_exclude_patterns
if l:buffer_name =~ pattern
if g:EditorConfig_verbose
echo 'Skipping EditorConfig for buffer "' . l:buffer_name .
\ '" based on pattern "' . pattern . '"'
endif
return
endif
endfor
" Check if any .editorconfig does exist
let l:conf_files = s:GetFilenames(l:buffer_path, '.editorconfig')
let l:conf_found = 0
for conf_file in conf_files
if filereadable(conf_file)
let l:conf_found = 1
break
endif
endfor
if !l:conf_found
return
endif
if !s:initialized
if s:Initialize()
return
endif
endif
if g:EditorConfig_verbose
echo 'Applying EditorConfig ' . s:editorconfig_core_mode .
\ ' on file "' . l:buffer_name . '"'
endif
if s:editorconfig_core_mode ==? 'vim_core'
if s:UseConfigFiles_VimCore(l:bufnr, l:buffer_name) == 0
call setbufvar(l:bufnr, 'editorconfig_applied', 1)
endif
elseif s:editorconfig_core_mode ==? 'external_command'
call s:UseConfigFiles_ExternalCommand(l:bufnr, l:buffer_name)
call setbufvar(l:bufnr, 'editorconfig_applied', 1)
else
echohl Error |
\ echo "Unknown EditorConfig Core: " .
\ s:editorconfig_core_mode |
\ echohl None
endif
endfunction " }}}1
" Custom commands, and autoloading {{{1
" Autocommands, and function to enable/disable the plugin {{{2
function! s:EditorConfigEnable(should_enable)
augroup editorconfig
autocmd!
if a:should_enable
autocmd BufNewFile,BufReadPost,BufFilePost * call s:UseConfigFiles(1)
autocmd VimEnter,BufNew * call s:UseConfigFiles(1)
endif
augroup END
endfunction
" }}}2
" Commands {{{2
command! EditorConfigEnable call s:EditorConfigEnable(1)
command! EditorConfigDisable call s:EditorConfigEnable(0)
command! EditorConfigReload call s:UseConfigFiles(0) " Reload EditorConfig files
" }}}2
" On startup, enable the autocommands
call s:EditorConfigEnable(1)
" }}}1
" UseConfigFiles function for different modes {{{1
function! s:UseConfigFiles_VimCore(bufnr, target)
" Use the vimscript EditorConfig core
try
let l:config = editorconfig_core#handler#get_configurations(
\ { 'target': a:target } )
call s:ApplyConfig(a:bufnr, l:config)
return 0 " success
catch
return 1 " failure
endtry
endfunction
function! s:UseConfigFiles_ExternalCommand(bufnr, target)
" Use external EditorConfig core (e.g., the C core)
call s:DisableShellSlash(a:bufnr)
let l:exec_path = shellescape(s:editorconfig_exec_path)
call s:ResetShellSlash(a:bufnr)
call s:SpawnExternalParser(a:bufnr, l:exec_path, a:target)
endfunction
function! s:SpawnExternalParser(bufnr, cmd, target) " {{{2
" Spawn external EditorConfig. Used by s:UseConfigFiles_ExternalCommand()
let l:cmd = a:cmd
if empty(l:cmd)
throw 'No cmd provided'
endif
let l:config = {}
call s:DisableShellSlash(a:bufnr)
let l:cmd = l:cmd . ' ' . shellescape(a:target)
call s:ResetShellSlash(a:bufnr)
let l:parsing_result = split(system(l:cmd), '\v[\r\n]+')
" if editorconfig core's exit code is not zero, give out an error
" message
if v:shell_error != 0
echohl ErrorMsg
echo 'Failed to execute "' . l:cmd . '". Exit code: ' .
\ v:shell_error
echo ''
echo 'Message:'
echo l:parsing_result
echohl None
return
endif
if g:EditorConfig_verbose
echo 'Output from EditorConfig core executable:'
echo l:parsing_result
endif
for one_line in l:parsing_result
let l:eq_pos = stridx(one_line, '=')
if l:eq_pos == -1 " = is not found. Skip this line
continue
endif
let l:eq_left = strpart(one_line, 0, l:eq_pos)
if l:eq_pos + 1 < strlen(one_line)
let l:eq_right = strpart(one_line, l:eq_pos + 1)
else
let l:eq_right = ''
endif
let l:config[l:eq_left] = l:eq_right
endfor
call s:ApplyConfig(a:bufnr, l:config)
endfunction " }}}2
" }}}1
" Set the buffer options {{{1
function! s:SetCharset(bufnr, charset) abort " apply config['charset']
" Remember the buffer's state so we can set `nomodifed` at the end
" if appropriate.
let l:orig_fenc = getbufvar(a:bufnr, "&fileencoding")
let l:orig_enc = getbufvar(a:bufnr, "&encoding")
let l:orig_modified = getbufvar(a:bufnr, "&modified")
if a:charset == "utf-8"
call setbufvar(a:bufnr, '&fileencoding', 'utf-8')
call setbufvar(a:bufnr, '&bomb', 0)
elseif a:charset == "utf-8-bom"
call setbufvar(a:bufnr, '&fileencoding', 'utf-8')
call setbufvar(a:bufnr, '&bomb', 1)
elseif a:charset == "latin1"
call setbufvar(a:bufnr, '&fileencoding', 'latin1')
call setbufvar(a:bufnr, '&bomb', 0)
elseif a:charset == "utf-16be"
call setbufvar(a:bufnr, '&fileencoding', 'utf-16be')
call setbufvar(a:bufnr, '&bomb', 1)
elseif a:charset == "utf-16le"
call setbufvar(a:bufnr, '&fileencoding', 'utf-16le')
call setbufvar(a:bufnr, '&bomb', 1)
endif
let l:new_fenc = getbufvar(a:bufnr, "&fileencoding")
" If all we did was change the fileencoding from the default to a copy
" of the default, we didn't actually modify the file.
if !l:orig_modified && (l:orig_fenc ==# '') && (l:new_fenc ==# l:orig_enc)
if g:EditorConfig_verbose
echo 'Setting nomodified on buffer ' . a:bufnr
endif
call setbufvar(a:bufnr, '&modified', 0)
endif
endfunction
function! s:ApplyConfig(bufnr, config) abort
if g:EditorConfig_verbose
echo 'Options: ' . string(a:config)
endif
if s:IsRuleActive('indent_style', a:config)
if a:config["indent_style"] == "tab"
call setbufvar(a:bufnr, '&expandtab', 0)
elseif a:config["indent_style"] == "space"
call setbufvar(a:bufnr, '&expandtab', 1)
endif
endif
if s:IsRuleActive('tab_width', a:config)
let l:tabstop = str2nr(a:config["tab_width"])
call setbufvar(a:bufnr, '&tabstop', l:tabstop)
else
" Grab the current ts so we can use it below
let l:tabstop = getbufvar(a:bufnr, '&tabstop')
endif
if s:IsRuleActive('indent_size', a:config)
" if indent_size is 'tab', set shiftwidth to tabstop;
" if indent_size is a positive integer, set shiftwidth to the integer
" value
if a:config["indent_size"] == "tab"
call setbufvar(a:bufnr, '&shiftwidth', l:tabstop)
if type(g:EditorConfig_softtabstop_tab) != type([])
call setbufvar(a:bufnr, '&softtabstop',
\ g:EditorConfig_softtabstop_tab > 0 ?
\ l:tabstop : g:EditorConfig_softtabstop_tab)
endif
else
let l:indent_size = str2nr(a:config["indent_size"])
if l:indent_size > 0
call setbufvar(a:bufnr, '&shiftwidth', l:indent_size)
if type(g:EditorConfig_softtabstop_space) != type([])
call setbufvar(a:bufnr, '&softtabstop',
\ g:EditorConfig_softtabstop_space > 0 ?
\ l:indent_size : g:EditorConfig_softtabstop_space)
endif
endif
endif
endif
if s:IsRuleActive('end_of_line', a:config) &&
\ getbufvar(a:bufnr, '&modifiable')
if a:config["end_of_line"] == "lf"
call setbufvar(a:bufnr, '&fileformat', 'unix')
elseif a:config["end_of_line"] == "crlf"
call setbufvar(a:bufnr, '&fileformat', 'dos')
elseif a:config["end_of_line"] == "cr"
call setbufvar(a:bufnr, '&fileformat', 'mac')
endif
endif
if s:IsRuleActive('charset', a:config) &&
\ getbufvar(a:bufnr, '&modifiable')
call s:SetCharset(a:bufnr, a:config["charset"])
endif
augroup editorconfig_trim_trailing_whitespace
autocmd! BufWritePre <buffer>
if s:IsRuleActive('trim_trailing_whitespace', a:config) &&
\ get(a:config, 'trim_trailing_whitespace', 'false') ==# 'true'
execute 'autocmd BufWritePre <buffer=' . a:bufnr . '> call s:TrimTrailingWhitespace()'
endif
augroup END
if s:IsRuleActive('insert_final_newline', a:config)
if exists('+fixendofline')
if a:config["insert_final_newline"] == "false"
call setbufvar(a:bufnr, '&fixendofline', 0)
else
call setbufvar(a:bufnr, '&fixendofline', 1)
endif
elseif exists(':SetNoEOL') == 2
if a:config["insert_final_newline"] == "false"
silent! SetNoEOL " Use the PreserveNoEOL plugin to accomplish it
endif
endif
endif
" highlight the columns following max_line_length
if s:IsRuleActive('max_line_length', a:config) &&
\ a:config['max_line_length'] != 'off'
let l:max_line_length = str2nr(a:config['max_line_length'])
if l:max_line_length >= 0
call setbufvar(a:bufnr, '&textwidth', l:max_line_length)
if g:EditorConfig_preserve_formatoptions == 0
" setlocal formatoptions+=tc
let l:fo = getbufvar(a:bufnr, '&formatoptions')
if l:fo !~# 't'
let l:fo .= 't'
endif
if l:fo !~# 'c'
let l:fo .= 'c'
endif
call setbufvar(a:bufnr, '&formatoptions', l:fo)
endif
endif
if exists('+colorcolumn')
if l:max_line_length > 0
if g:EditorConfig_max_line_indicator == 'line'
" setlocal colorcolumn+=+1
let l:cocol = getbufvar(a:bufnr, '&colorcolumn')
if !empty(l:cocol)
let l:cocol .= ','
endif
let l:cocol .= '+1'
call setbufvar(a:bufnr, '&colorcolumn', l:cocol)
elseif g:EditorConfig_max_line_indicator == 'fill' &&
\ l:max_line_length < getbufvar(a:bufnr, '&columns')
" Fill only if the columns of screen is large enough
call setbufvar(a:bufnr, '&colorcolumn',
\ join(range(l:max_line_length+1,
\ getbufvar(a:bufnr, '&columns')),
\ ','))
elseif g:EditorConfig_max_line_indicator == 'exceeding'
call setbufvar(a:bufnr, '&colorcolumn', '')
for l:match in getmatches()
if get(l:match, 'group', '') == 'ColorColumn'
call matchdelete(get(l:match, 'id'))
endif
endfor
call matchadd('ColorColumn',
\ '\%' . (l:max_line_length + 1) . 'v.', 100)
elseif g:EditorConfig_max_line_indicator == 'fillexceeding'
let &l:colorcolumn = ''
for l:match in getmatches()
if get(l:match, 'group', '') == 'ColorColumn'
call matchdelete(get(l:match, 'id'))
endif
endfor
call matchadd('ColorColumn',
\ '\%'. (l:max_line_length + 1) . 'v.\+', -1)
endif
endif
endif
endif
call editorconfig#ApplyHooks(a:config)
endfunction
" }}}1
function! s:TrimTrailingWhitespace() " {{{1
" Called from within a buffer-specific autocmd, so we can use '%'
if getbufvar('%', '&modifiable')
" don't lose user position when trimming trailing whitespace
let s:view = winsaveview()
try
silent! keeppatterns keepjumps %s/\s\+$//e
finally
call winrestview(s:view)
endtry
endif
endfunction " }}}1
function! s:IsRuleActive(name, config) " {{{1
return index(g:EditorConfig_disable_rules, a:name) < 0 &&
\ has_key(a:config, a:name)
endfunction "}}}1
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vim: fdm=marker fdc=3

View File

@@ -0,0 +1,953 @@
vim9script noclear
# Config {{{1
var SHELL_PROMPT: string = ''
def UpdateUserSettings() #{{{2
var new_prompt: string = g:
->get('helptoc', {})
->get('shell_prompt', '^\w\+@\w\+:\f\+\$\s')
if new_prompt != SHELL_PROMPT
SHELL_PROMPT = new_prompt
# invalidate cache: user config has changed
unlet! b:toc
endif
enddef
UpdateUserSettings()
# Init {{{1
const HELP_TEXT: list<string> =<< trim END
normal commands in help window
──────────────────────────────
? hide this help window
<C-J> scroll down one line
<C-K> scroll up one line
normal commands in TOC menu
───────────────────────────
j select next entry
k select previous entry
J same as j, and jump to corresponding line in main buffer
K same as k, and jump to corresponding line in main buffer
c select nearest entry from cursor position in main buffer
g select first entry
G select last entry
H collapse one level
L expand one level
p print selected entry on command-line
P same as p but automatically, whenever selection changes
press multiple times to toggle feature on/off
q quit menu
z redraw menu with selected entry at center
+ increase width of popup menu
- decrease width of popup menu
/ look for given text with fuzzy algorithm
? show help window
<C-D> scroll down half a page
<C-U> scroll up half a page
<PageUp> scroll down a whole page
<PageDown> scroll up a whole page
<Home> select first entry
<End> select last entry
title meaning
─────────────
example: 12/34 (5/6)
broken down:
12 index of selected entry
34 index of last entry
5 index of deepest level currently visible
6 index of maximum possible level
tip
───
after inserting a pattern to look for with the / command,
if you press <Esc> instead of <CR>, you can then get
more context for each remaining entry by pressing J or K
END
const MATCH_ENTRY: dict<dict<func: bool>> = {
help: {},
man: {
1: (line: string, _): bool => line =~ '^\S',
2: (line: string, _): bool => line =~ '^\%( \{3\}\)\=\S',
3: (line: string, _): bool => line =~ '^\s\+\(\%(+\|-\)\S\+,\s\+\)*\%(+\|-\)\S\+',
},
markdown: {
1: (line: string, nextline: string): bool =>
(line =~ '^#[^#]' || nextline =~ '^=\+$') && line =~ '\w',
2: (line: string, nextline: string): bool =>
(line =~ '^##[^#]' || nextline =~ '^-\+$') && line =~ '\w',
3: (line: string, _): bool => line =~ '^###[^#]',
4: (line: string, _): bool => line =~ '^####[^#]',
5: (line: string, _): bool => line =~ '^#####[^#]',
6: (line: string, _): bool => line =~ '^######[^#]',
},
terminal: {
1: (line: string, _): bool => line =~ SHELL_PROMPT,
}
}
const HELP_RULERS: dict<string> = {
'=': '^=\{40,}$',
'-': '^-\{40,}',
}
const HELP_RULER: string = HELP_RULERS->values()->join('\|')
# the regex is copied from the help syntax plugin
const HELP_TAG: string = '\*[#-)!+-~]\+\*\%(\s\|$\)\@='
# Adapted from `$VIMRUNTIME/syntax/help.vim`.{{{
#
# The original regex is:
#
# ^[-A-Z .][-A-Z0-9 .()_]*\ze\(\s\+\*\|$\)
#
# Allowing a space or a hyphen at the start can give false positives, and is
# useless, so we don't allow them.
#}}}
const HELP_HEADLINE: string = '^\C[A-Z.][-A-Z0-9 .()_]*\%(\s\+\*+\@!\|$\)'
# ^--^
# To prevent some false positives under `:help feature-list`.
var lvls: dict<number>
def InitHelpLvls()
lvls = {
'*01.1*': 0,
'1.': 0,
'1.2': 0,
'1.2.3': 0,
'header ~': 0,
HEADLINE: 0,
tag: 0,
}
enddef
const AUGROUP: string = 'HelpToc'
var fuzzy_entries: list<dict<any>>
var help_winid: number
var print_entry: bool
var selected_entry_match: number
# Interface {{{1
export def Open() #{{{2
var type: string = GetType()
if !MATCH_ENTRY->has_key(type)
return
endif
if type == 'terminal' && win_gettype() == 'popup'
# trying to deal with a popup menu on top of a popup terminal seems
# too tricky for now
echomsg 'does not work in a popup window; only in a regular window'
return
endif
UpdateUserSettings()
# invalidate the cache if the buffer's contents has changed
if exists('b:toc') && &filetype != 'man'
if b:toc.changedtick != b:changedtick
# in a terminal buffer, `b:changedtick` does not change
|| type == 'terminal' && line('$') > b:toc.linecount
unlet! b:toc
endif
endif
if !exists('b:toc')
SetToc()
endif
var winpos: list<number> = winnr()->win_screenpos()
var height: number = winheight(0) - 2
var width: number = winwidth(0)
b:toc.width = b:toc.width ?? width / 3
# the popup needs enough space to display the help message in its title
if b:toc.width < 30
b:toc.width = 30
endif
# Is `popup_menu()` OK with a list of dictionaries?{{{
#
# Yes, see `:help popup_create-arguments`.
# Although, it expects dictionaries with the keys `text` and `props`.
# But we use dictionaries with the keys `text` and `lnum`.
# IOW, we abuse the feature which lets us use text properties in a popup.
#}}}
var winid: number = GetTocEntries()
->popup_menu({
line: winpos[0],
col: winpos[1] + width - 1,
pos: 'topright',
scrollbar: false,
highlight: type == 'terminal' ? 'Terminal' : 'Normal',
border: [],
borderchars: ['─', '│', '─', '│', '┌', '┐', '┘', '└'],
minheight: height,
maxheight: height,
minwidth: b:toc.width,
maxwidth: b:toc.width,
filter: Filter,
callback: Callback,
})
Win_execute(winid, [$'ownsyntax {&filetype}', '&l:conceallevel = 3'])
# In a help file, we might reduce some noisy tags to a trailing asterisk.
# Hide those.
if type == 'help'
matchadd('Conceal', '\*$', 0, -1, {window: winid})
endif
SelectNearestEntryFromCursor(winid)
# can't set the title before jumping to the relevant line, otherwise the
# indicator in the title might be wrong
SetTitle(winid)
enddef
#}}}1
# Core {{{1
def SetToc() #{{{2
var toc: dict<any> = {entries: []}
var type: string = GetType()
toc.changedtick = b:changedtick
if !toc->has_key('width')
toc.width = 0
endif
# We cache the toc in `b:toc` to get better performance.{{{
#
# Without caching, when we press `H`, `L`, `H`, `L`, ... quickly for a few
# seconds, there is some lag if we then try to move with `j` and `k`.
# This can only be perceived in big man pages like with `:Man ffmpeg-all`.
#}}}
b:toc = toc
if type == 'help'
SetTocHelp()
return
endif
if type == 'terminal'
b:toc.linecount = line('$')
endif
var curline: string = getline(1)
var nextline: string
var lvl_and_test: list<list<any>> = MATCH_ENTRY
->get(type, {})
->items()
->sort((l: list<any>, ll: list<any>): number => l[0]->str2nr() - ll[0]->str2nr())
for lnum: number in range(1, line('$'))
nextline = getline(lnum + 1)
for [lvl: string, IsEntry: func: bool] in lvl_and_test
if IsEntry(curline, nextline)
b:toc.entries->add({
lnum: lnum,
lvl: lvl->str2nr(),
text: curline,
})
break
endif
endfor
curline = nextline
endfor
InitMaxAndCurLvl()
enddef
def SetTocHelp() #{{{2
var main_ruler: string
for line: string in getline(1, '$')
if line =~ HELP_RULER
main_ruler = line =~ '=' ? HELP_RULERS['='] : HELP_RULERS['-']
break
endif
endfor
var prevline: string
var curline: string = getline(1)
var nextline: string
var in_list: bool
var last_numbered_entry: number
InitHelpLvls()
for lnum: number in range(1, line('$'))
nextline = getline(lnum + 1)
if main_ruler != '' && curline =~ main_ruler
last_numbered_entry = 0
# The information gathered in `lvls` might not be applicable to all
# the main sections of a help file. Let's reset it whenever we find
# a ruler.
InitHelpLvls()
endif
# Do not assume that a list ends on an empty line.
# See the list at `:help gdb` for a counter-example.
if in_list
&& curline !~ '^\d\+.\s'
&& curline !~ '^\s*$'
&& curline !~ '^[< \t]'
in_list = false
endif
if prevline =~ '^\d\+\.\s'
&& curline !~ '^\s*$'
&& curline !~ $'^\s*{HELP_TAG}'
in_list = true
endif
# 1.
if prevline =~ '^\d\+\.\s'
# let's assume that the start of a main entry is always followed by an
# empty line, or a line starting with a tag
&& (curline =~ '^>\=\s*$' || curline =~ $'^\s*{HELP_TAG}')
# ignore a numbered line in a list
&& !in_list
var current_numbered_entry: number = prevline
->matchstr('^\d\+\ze\.\s')
->str2nr()
if current_numbered_entry > last_numbered_entry
AddEntryInTocHelp('1.', lnum - 1, prevline)
last_numbered_entry = prevline
->matchstr('^\d\+\ze\.\s')
->str2nr()
endif
endif
# 1.2
if curline =~ '^\d\+\.\d\+\s'
if curline =~ $'\%({HELP_TAG}\s*\|\~\)$'
|| (prevline =~ $'^\s*{HELP_TAG}' || nextline =~ $'^\s*{HELP_TAG}')
|| (prevline =~ HELP_RULER || nextline =~ HELP_RULER)
|| (prevline =~ '^\s*$' && nextline =~ '^\s*$')
AddEntryInTocHelp('1.2', lnum, curline)
endif
# 1.2.3
elseif curline =~ '^\s\=\d\+\.\d\+\.\d\+\s'
AddEntryInTocHelp('1.2.3', lnum, curline)
endif
# HEADLINE
if curline =~ HELP_HEADLINE
&& curline !~ '^CTRL-'
&& prevline->IsSpecialHelpLine()
&& (nextline->IsSpecialHelpLine() || nextline =~ '^\s*(\|^\t\|^N[oO][tT][eE]:')
AddEntryInTocHelp('HEADLINE', lnum, curline)
endif
# header ~
if curline =~ '\~$'
&& curline =~ '\w'
&& curline !~ '^[ \t<]\|\t\|---+---\|^NOTE:'
&& curline !~ '^\d\+\.\%(\d\+\%(\.\d\+\)\=\)\=\s'
&& prevline !~ $'^\s*{HELP_TAG}'
&& prevline !~ '\~$'
&& nextline !~ '\~$'
AddEntryInTocHelp('header ~', lnum, curline)
endif
# *some_tag*
if curline =~ HELP_TAG
AddEntryInTocHelp('tag', lnum, curline)
endif
# In the Vim user manual, a main section is a special case.{{{
#
# It's not a simple numbered section:
#
# 01.1
#
# It's used as a tag:
#
# *01.1* Two manuals
# ^ ^
#}}}
if prevline =~ main_ruler && curline =~ '^\*\d\+\.\d\+\*'
AddEntryInTocHelp('*01.1*', lnum, curline)
endif
[prevline, curline] = [curline, nextline]
endfor
# let's ignore the tag on the first line (not really interesting)
if b:toc.entries->get(0, {})->get('lnum') == 1
b:toc.entries->remove(0)
endif
# let's also ignore anything before the first `1.` line
var i: number = b:toc.entries
->copy()
->map((_, entry: dict<any>) => entry.text)
->match('^\s*1\.\s')
if i > 0
b:toc.entries->remove(0, i - 1)
endif
InitMaxAndCurLvl()
# set level of tag entries to the deepest level
var has_tag: bool = b:toc.entries
->copy()
->map((_, entry: dict<any>) => entry.text)
->match(HELP_TAG) >= 0
if has_tag
++b:toc.maxlvl
endif
b:toc.entries
->map((_, entry: dict<any>) => entry.lvl == 0
? entry->extend({lvl: b:toc.maxlvl})
: entry)
# fix indentation
var min_lvl: number = b:toc.entries
->copy()
->map((_, entry: dict<any>) => entry.lvl)
->min()
for entry: dict<any> in b:toc.entries
entry.text = entry.text
->substitute('^\s*', () => repeat(' ', (entry.lvl - min_lvl) * 3), '')
endfor
enddef
def AddEntryInTocHelp(type: string, lnum: number, line: string) #{{{2
# don't add a duplicate entry
if lnum == b:toc.entries->get(-1, {})->get('lnum')
# For a numbered line containing a tag, *do* add an entry.
# But only for its numbered prefix, not for its tag.
# The former is the line's most meaningful representation.
if b:toc.entries->get(-1, {})->get('type') == 'tag'
b:toc.entries->remove(-1)
else
return
endif
endif
var text: string = line
if type == 'tag'
var tags: list<string>
text->substitute(HELP_TAG, () => !!tags->add(submatch(0)), 'g')
text = tags
# we ignore errors and warnings because those are meaningless in
# a TOC where no context is available
->filter((_, tag: string) => tag !~ '\*[EW]\d\+\*')
->join()
if text !~ HELP_TAG
return
endif
endif
var maxlvl: number = lvls->values()->max()
if type == 'tag'
lvls[type] = 0
elseif type == '1.2'
lvls[type] = lvls[type] ?? lvls->get('1.', maxlvl) + 1
elseif type == '1.2.3'
lvls[type] = lvls[type] ?? lvls->get('1.2', maxlvl) + 1
else
lvls[type] = lvls[type] ?? maxlvl + 1
endif
# Ignore noisy tags.{{{
#
# 14. Linking groups *:hi-link* *:highlight-link* *E412* *E413*
# ^----------------------------------------^
# ^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\zs\*.*
# ---
#
# We don't use conceal because then, `matchfuzzypos()` could match concealed
# characters, which would be confusing.
#}}}
# MAKING YOUR OWN SYNTAX FILES *mysyntaxfile*
# ^------------^
# ^\s*[A-Z].\{-}\*\zs.*
#
var after_HEADLINE: string = '^\s*[A-Z].\{-}\*\zs.*'
# 14. Linking groups *:hi-link* *:highlight-link* *E412* *E413*
# ^----------------------------------------^
# ^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\*\zs.*
var after_numbered: string = '^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\*\zs.*'
# 01.3 Using the Vim tutor *tutor* *vimtutor*
# ^----------------^
var after_numbered_tutor: string = '^\*\d\+\.\%(\d\+\.\=\)*.\{-}\t\*\zs.*'
var noisy_tags: string = $'{after_HEADLINE}\|{after_numbered}\|{after_numbered_tutor}'
text = text->substitute(noisy_tags, '', '')
# We don't remove the trailing asterisk, because the help syntax plugin
# might need it to highlight some headlines.
b:toc.entries->add({
lnum: lnum,
lvl: lvls[type],
text: text,
type: type,
})
enddef
def InitMaxAndCurLvl() #{{{2
b:toc.maxlvl = b:toc.entries
->copy()
->map((_, entry: dict<any>) => entry.lvl)
->max()
b:toc.curlvl = b:toc.maxlvl
enddef
def Popup_settext(winid: number, entries: list<dict<any>>) #{{{2
var text: list<any>
# When we fuzzy search the toc, the dictionaries in `entries` contain a
# `props` key, to highlight each matched character individually.
# We don't want to process those dictionaries further.
# The processing should already have been done by the caller.
if entries->get(0, {})->has_key('props')
text = entries
else
text = entries
->copy()
->map((_, entry: dict<any>): string => entry.text)
endif
popup_settext(winid, text)
SetTitle(winid)
redraw
enddef
def SetTitle(winid: number) #{{{2
var curlnum: number
var lastlnum: number = line('$', winid)
var is_empty: bool = lastlnum == 1
&& winid->winbufnr()->getbufoneline(1) == ''
if is_empty
[curlnum, lastlnum] = [0, 0]
else
curlnum = line('.', winid)
endif
var newtitle: string = printf(' %*d/%d (%d/%d)',
len(lastlnum), curlnum,
lastlnum,
b:toc.curlvl,
b:toc.maxlvl,
)
var width: number = winid->popup_getoptions().minwidth
newtitle = printf('%s%*s',
newtitle,
width - newtitle->strlen(),
'press ? for help ')
popup_setoptions(winid, {title: newtitle})
enddef
def SelectNearestEntryFromCursor(winid: number) #{{{2
var lnum: number = line('.')
var firstline: number = b:toc.entries
->copy()
->filter((_, line: dict<any>): bool => line.lvl <= b:toc.curlvl && line.lnum <= lnum)
->len()
if firstline == 0
return
endif
Win_execute(winid, $'normal! {firstline}Gzz')
enddef
def Filter(winid: number, key: string): bool #{{{2
# support various normal commands for moving/scrolling
if [
'j', 'J', 'k', 'K', "\<Down>", "\<Up>", "\<C-N>", "\<C-P>",
"\<C-D>", "\<C-U>",
"\<PageUp>", "\<PageDown>",
'g', 'G', "\<Home>", "\<End>",
'z'
]->index(key) >= 0
var scroll_cmd: string = {
J: 'j',
K: 'k',
g: '1G',
"\<Home>": '1G',
"\<End>": 'G',
z: 'zz'
}->get(key, key)
var old_lnum: number = line('.', winid)
Win_execute(winid, $'normal! {scroll_cmd}')
var new_lnum: number = line('.', winid)
if print_entry
PrintEntry(winid)
endif
# wrap around the edges
if new_lnum == old_lnum
scroll_cmd = {
j: '1G',
J: '1G',
k: 'G',
K: 'G',
"\<Down>": '1G',
"\<Up>": 'G',
"\<C-N>": '1G',
"\<C-P>": 'G',
}->get(key, '')
if !scroll_cmd->empty()
Win_execute(winid, $'normal! {scroll_cmd}')
endif
endif
# move the cursor to the corresponding line in the main buffer
if key == 'J' || key == 'K'
var lnum: number = GetBufLnum(winid)
execute $'normal! 0{lnum}zt'
# install a match in the regular buffer to highlight the position of
# the entry in the latter
MatchDelete()
selected_entry_match = matchaddpos('IncSearch', [lnum], 0, -1)
endif
SetTitle(winid)
return true
elseif key == 'c'
SelectNearestEntryFromCursor(winid)
return true
# when we press `p`, print the selected line (useful when it's truncated)
elseif key == 'p'
PrintEntry(winid)
return true
# same thing, but automatically
elseif key == 'P'
print_entry = !print_entry
if print_entry
PrintEntry(winid)
else
echo ''
endif
return true
elseif key == 'q'
popup_close(winid, -1)
return true
elseif key == '?'
ToggleHelp(winid)
return true
# scroll help window
elseif key == "\<C-J>" || key == "\<C-K>"
var scroll_cmd: string = {"\<C-J>": 'j', "\<C-K>": 'k'}->get(key, key)
if scroll_cmd == 'j' && line('.', help_winid) == line('$', help_winid)
scroll_cmd = '1G'
elseif scroll_cmd == 'k' && line('.', help_winid) == 1
scroll_cmd = 'G'
endif
Win_execute(help_winid, $'normal! {scroll_cmd}')
return true
# increase/decrease the popup's width
elseif key == '+' || key == '-'
var width: number = winid->popup_getoptions().minwidth
if key == '-' && width == 1
|| key == '+' && winid->popup_getpos().col == 1
return true
endif
width = width + (key == '+' ? 1 : -1)
# remember the last width if we close and re-open the TOC later
b:toc.width = width
popup_setoptions(winid, {minwidth: width, maxwidth: width})
return true
elseif key == 'H' && b:toc.curlvl > 1
|| key == 'L' && b:toc.curlvl < b:toc.maxlvl
CollapseOrExpand(winid, key)
return true
elseif key == '/'
# This is probably what the user expect if they've started a first fuzzy
# search, press Escape, then start a new one.
DisplayNonFuzzyToc(winid)
[{
group: AUGROUP,
event: 'CmdlineChanged',
pattern: '@',
cmd: $'FuzzySearch({winid})',
replace: true,
}, {
group: AUGROUP,
event: 'CmdlineLeave',
pattern: '@',
cmd: 'TearDown()',
replace: true,
}]->autocmd_add()
# Need to evaluate `winid` right now with an `eval`'ed and `execute()`'ed heredoc because:{{{
#
# - the mappings can only access the script-local namespace
# - `winid` is in the function namespace; not in the script-local one
#}}}
var input_mappings: list<string> =<< trim eval END
cnoremap <buffer><nowait> <Down> <ScriptCmd>Filter({winid}, 'j')<CR>
cnoremap <buffer><nowait> <Up> <ScriptCmd>Filter({winid}, 'k')<CR>
cnoremap <buffer><nowait> <C-N> <ScriptCmd>Filter({winid}, 'j')<CR>
cnoremap <buffer><nowait> <C-P> <ScriptCmd>Filter({winid}, 'k')<CR>
END
input_mappings->execute()
var look_for: string
try
popup_setoptions(winid, {mapping: true})
look_for = input('look for: ', '', $'custom,{Complete->string()}') | redraw | echo ''
catch /Vim:Interrupt/
TearDown()
finally
popup_setoptions(winid, {mapping: false})
endtry
return look_for == '' ? true : popup_filter_menu(winid, "\<CR>")
endif
return popup_filter_menu(winid, key)
enddef
def FuzzySearch(winid: number) #{{{2
var look_for: string = getcmdline()
if look_for == ''
DisplayNonFuzzyToc(winid)
return
endif
# We match against *all* entries; not just the currently visible ones.
# Rationale: If we use a (fuzzy) search, we're probably lost. We don't know
# where the info is.
var matches: list<list<any>> = b:toc.entries
->copy()
->matchfuzzypos(look_for, {key: 'text'})
fuzzy_entries = matches->get(0, [])->copy()
var pos: list<list<number>> = matches->get(1, [])
var text: list<dict<any>>
if !has('textprop')
text = matches->get(0, [])
else
var buf: number = winid->winbufnr()
if prop_type_get('help-fuzzy-toc', {bufnr: buf}) == {}
prop_type_add('help-fuzzy-toc', {
bufnr: buf,
combine: false,
highlight: 'IncSearch',
})
endif
text = matches
->get(0, [])
->map((i: number, match: dict<any>) => ({
text: match.text,
props: pos[i]->copy()->map((_, col: number) => ({
col: col + 1,
length: 1,
type: 'help-fuzzy-toc',
}))}))
endif
Win_execute(winid, 'normal! 1Gzt')
Popup_settext(winid, text)
enddef
def DisplayNonFuzzyToc(winid: number) #{{{2
fuzzy_entries = null_list
Popup_settext(winid, GetTocEntries())
enddef
def PrintEntry(winid: number) #{{{2
echo GetTocEntries()[line('.', winid) - 1]['text']
enddef
def CollapseOrExpand(winid: number, key: string) #{{{2
# Must be saved before we reset the popup contents, so we can
# automatically select the least unexpected entry in the updated popup.
var buf_lnum: number = GetBufLnum(winid)
# find the nearest lower level for which the contents of the TOC changes
if key == 'H'
while b:toc.curlvl > 1
var old: list<dict<any>> = GetTocEntries()
--b:toc.curlvl
var new: list<dict<any>> = GetTocEntries()
# In `:help`, there are only entries in levels 3.
# We don't want to collapse to level 2, nor 1.
# It would clear the TOC which is confusing.
if new->empty()
++b:toc.curlvl
break
endif
var did_change: bool = new != old
if did_change || b:toc.curlvl == 1
break
endif
endwhile
# find the nearest upper level for which the contents of the TOC changes
else
while b:toc.curlvl < b:toc.maxlvl
var old: list<dict<any>> = GetTocEntries()
++b:toc.curlvl
var did_change: bool = GetTocEntries() != old
if did_change || b:toc.curlvl == b:toc.maxlvl
break
endif
endwhile
endif
# update the popup contents
var toc_entries: list<dict<any>> = GetTocEntries()
Popup_settext(winid, toc_entries)
# Try to select the same entry; if it's no longer visible, select its
# direct parent.
var toc_lnum: number = 0
for entry: dict<any> in toc_entries
if entry.lnum > buf_lnum
break
endif
++toc_lnum
endfor
Win_execute(winid, $'normal! {toc_lnum ?? 1}Gzz')
enddef
def MatchDelete() #{{{2
if selected_entry_match == 0
return
endif
selected_entry_match->matchdelete()
selected_entry_match = 0
enddef
def Callback(winid: number, choice: number) #{{{2
MatchDelete()
if help_winid != 0
help_winid->popup_close()
help_winid = 0
endif
if choice == -1
fuzzy_entries = null_list
return
endif
var lnum: number = GetTocEntries()
->get(choice - 1, {})
->get('lnum')
fuzzy_entries = null_list
if lnum == 0
return
endif
cursor(lnum, 1)
normal! zvzt
enddef
def ToggleHelp(menu_winid: number) #{{{2
if help_winid == 0
var height: number = [HELP_TEXT->len(), winheight(0) * 2 / 3]->min()
var longest_line: number = HELP_TEXT
->copy()
->map((_, line: string) => line->strcharlen())
->max()
var width: number = [longest_line, winwidth(0) * 2 / 3]->min()
var pos: dict<number> = popup_getpos(menu_winid)
var [line: number, col: number] = [pos.line, pos.col]
--col
var zindex: number = popup_getoptions(menu_winid).zindex
++zindex
help_winid = HELP_TEXT->popup_create({
line: line,
col: col,
pos: 'topright',
minheight: height,
maxheight: height,
minwidth: width,
maxwidth: width,
border: [],
borderchars: ['─', '│', '─', '│', '┌', '┐', '┘', '└'],
highlight: &buftype == 'terminal' ? 'Terminal' : 'Normal',
scrollbar: false,
zindex: zindex,
})
setwinvar(help_winid, '&cursorline', true)
setwinvar(help_winid, '&linebreak', true)
matchadd('Special', '^<\S\+\|^\S\{,2} \@=', 0, -1, {window: help_winid})
matchadd('Number', '\d\+', 0, -1, {window: help_winid})
for lnum: number in HELP_TEXT->len()->range()
if HELP_TEXT[lnum] =~ '^─\+$'
matchaddpos('Title', [lnum], 0, -1, {window: help_winid})
endif
endfor
else
if IsVisible(help_winid)
popup_hide(help_winid)
else
popup_show(help_winid)
endif
endif
enddef
def Win_execute(winid: number, cmd: any) #{{{2
# wrapper around `win_execute()` to enforce a redraw, which might be necessary
# whenever we change the cursor position
win_execute(winid, cmd)
redraw
enddef
def TearDown() #{{{2
autocmd_delete([{group: AUGROUP}])
cunmap <buffer> <Down>
cunmap <buffer> <Up>
cunmap <buffer> <C-N>
cunmap <buffer> <C-P>
enddef
#}}}1
# Util {{{1
def GetType(): string #{{{2
return &buftype == 'terminal' ? 'terminal' : &filetype
enddef
def GetTocEntries(): list<dict<any>> #{{{2
return fuzzy_entries ?? b:toc.entries
->copy()
->filter((_, entry: dict<any>): bool => entry.lvl <= b:toc.curlvl)
enddef
def GetBufLnum(winid: number): number #{{{2
var toc_lnum: number = line('.', winid)
return GetTocEntries()
->get(toc_lnum - 1, {})
->get('lnum')
enddef
def IsVisible(win: number): bool #{{{2
return win->popup_getpos()->get('visible')
enddef
def IsSpecialHelpLine(line: string): bool #{{{2
return line =~ '^[<>]\=\s*$'
|| line =~ '^\s*\*'
|| line =~ HELP_RULER
|| line =~ HELP_HEADLINE
enddef
def Complete(..._): string #{{{2
return b:toc.entries
->copy()
->map((_, entry: dict<any>) => entry.text->trim(' ~')->substitute('*', '', 'g'))
->filter((_, text: string): bool => text =~ '^[-a-zA-Z0-9_() ]\+$')
->sort()
->uniq()
->join("\n")
enddef

View File

@@ -0,0 +1,5 @@
vim9script noclear
import autoload '../autoload/helptoc.vim'
command -bar HelpToc helptoc.Open()

View File

@@ -0,0 +1,316 @@
" Function to left and right align text.
"
" Written by: Preben "Peppe" Guldberg <c928400@student.dtu.dk>
" Created: 980806 14:13 (or around that time anyway)
" Revised: 001103 00:36 (See "Revisions" below)
" function Justify( [ textwidth [, maxspaces [, indent] ] ] )
"
" Justify() will left and right align a line by filling in an
" appropriate amount of spaces. Extra spaces are added to existing
" spaces starting from the right side of the line. As an example, the
" following documentation has been justified.
"
" The function takes the following arguments:
" textwidth argument
" ------------------
" If not specified, the value of the 'textwidth' option is used. If
" 'textwidth' is zero a value of 80 is used.
"
" Additionally the arguments 'tw' and '' are accepted. The value of
" 'textwidth' will be used. These are handy, if you just want to specify
" the maxspaces argument.
" maxspaces argument
" ------------------
" If specified, alignment will only be done, if the longest space run
" after alignment is no longer than maxspaces.
"
" An argument of '' is accepted, should the user like to specify all
" arguments.
"
" To aid user defined commands, negative values are accepted aswell.
" Using a negative value specifies the default behaviour: any length of
" space runs will be used to justify the text.
" indent argument
" ---------------
" This argument specifies how a line should be indented. The default is
" to keep the current indentation.
"
" Negative values: Keep current amount of leading whitespace.
" Positive values: Indent all lines with leading whitespace using this
" amount of whitespace.
"
" Note that the value 0, needs to be quoted as a string. This value
" leads to a left flushed text.
"
" Additionally units of 'shiftwidth'/'sw' and 'tabstop'/'ts' may be
" added. In this case, if the value of indent is positive, the amount of
" whitespace to be added will be multiplied by the value of the
" 'shiftwidth' and 'tabstop' settings. If these units are used, the
" argument must be given as a string, eg. Justify('','','2sw').
"
" If the values of 'sw' or 'tw' are negative, they are treated as if
" they were 0, which means that the text is flushed left. There is no
" check if a negative number prefix is used to change the sign of a
" negative 'sw' or 'ts' value.
"
" As with the other arguments, '' may be used to get the default
" behaviour.
" Notes:
"
" If the line, adjusted for space runs and leading/trailing whitespace,
" is wider than the used textwidth, the line will be left untouched (no
" whitespace removed). This should be equivalent to the behaviour of
" :left, :right and :center.
"
" If the resulting line is shorter than the used textwidth it is left
" untouched.
"
" All space runs in the line are truncated before the alignment is
" carried out.
"
" If you have set 'noexpandtab', :retab! is used to replace space runs
" with whitespace using the value of 'tabstop'. This should be
" conformant with :left, :right and :center.
"
" If joinspaces is set, an extra space is added after '.', '?' and '!'.
" If 'cpoptions' include 'j', extra space is only added after '.'.
" (This may on occasion conflict with maxspaces.)
" Related mappings:
"
" Mappings that will align text using the current text width, using at
" most four spaces in a space run and keeping current indentation.
nmap _j :%call Justify('tw',4)<CR>
vmap _j :call Justify('tw',4)<CR>
"
" Mappings that will remove space runs and format lines (might be useful
" prior to aligning the text).
nmap ,gq :%s/\s\+/ /g<CR>gq1G
vmap ,gq :s/\s\+/ /g<CR>gvgq
" User defined command:
"
" The following is an ex command that works as a shortcut to the Justify
" function. Arguments to Justify() can be added after the command.
com! -range -nargs=* Justify <line1>,<line2>call Justify(<f-args>)
"
" The following commands are all equivalent:
"
" 1. Simplest use of Justify():
" :call Justify()
" :Justify
"
" 2. The _j mapping above via the ex command:
" :%Justify tw 4
"
" 3. Justify visualised text at 72nd column while indenting all
" previously indented text two shiftwidths
" :'<,'>call Justify(72,'','2sw')
" :'<,'>Justify 72 -1 2sw
"
" This documentation has been justified using the following command:
":se et|kz|1;/^" function Justify(/+,'z-g/^" /s/^" //|call Justify(70,3)|s/^/" /
" Revisions:
" 001103: If 'joinspaces' was set, calculations could be wrong.
" Tabs at start of line could also lead to errors.
" Use setline() instead of "exec 's/foo/bar/' - safer.
" Cleaned up the code a bit.
"
" Todo: Convert maps to the new script specific form
" Error function
function! Justify_error(message)
echohl Error
echo "Justify([tw, [maxspaces [, indent]]]): " . a:message
echohl None
endfunction
" Now for the real thing
function! Justify(...) range
if a:0 > 3
call Justify_error("Too many arguments (max 3)")
return 1
endif
" Set textwidth (accept 'tw' and '' as arguments)
if a:0 >= 1
if a:1 =~ '^\(tw\)\=$'
let tw = &tw
elseif a:1 =~ '^\d\+$'
let tw = a:1
else
call Justify_error("tw must be a number (>0), '' or 'tw'")
return 2
endif
else
let tw = &tw
endif
if tw == 0
let tw = 80
endif
" Set maximum number of spaces between WORDs
if a:0 >= 2
if a:2 == ''
let maxspaces = tw
elseif a:2 =~ '^-\d\+$'
let maxspaces = tw
elseif a:2 =~ '^\d\+$'
let maxspaces = a:2
else
call Justify_error("maxspaces must be a number or ''")
return 3
endif
else
let maxspaces = tw
endif
if maxspaces <= 1
call Justify_error("maxspaces should be larger than 1")
return 4
endif
" Set the indentation style (accept sw and ts units)
let indent_fix = ''
if a:0 >= 3
if (a:3 == '') || a:3 =~ '^-[1-9]\d*\(shiftwidth\|sw\|tabstop\|ts\)\=$'
let indent = -1
elseif a:3 =~ '^-\=0\(shiftwidth\|sw\|tabstop\|ts\)\=$'
let indent = 0
elseif a:3 =~ '^\d\+\(shiftwidth\|sw\|tabstop\|ts\)\=$'
let indent = substitute(a:3, '\D', '', 'g')
elseif a:3 =~ '^\(shiftwidth\|sw\|tabstop\|ts\)$'
let indent = 1
else
call Justify_error("indent: a number with 'sw'/'ts' unit")
return 5
endif
if indent >= 0
while indent > 0
let indent_fix = indent_fix . ' '
let indent = indent - 1
endwhile
let indent_sw = 0
if a:3 =~ '\(shiftwidth\|sw\)'
let indent_sw = &sw
elseif a:3 =~ '\(tabstop\|ts\)'
let indent_sw = &ts
endif
let indent_fix2 = ''
while indent_sw > 0
let indent_fix2 = indent_fix2 . indent_fix
let indent_sw = indent_sw - 1
endwhile
let indent_fix = indent_fix2
endif
else
let indent = -1
endif
" Avoid substitution reports
let save_report = &report
set report=1000000
" Check 'joinspaces' and 'cpo'
if &js == 1
if &cpo =~ 'j'
let join_str = '\(\. \)'
else
let join_str = '\([.!?!] \)'
endif
endif
let cur = a:firstline
while cur <= a:lastline
let str_orig = getline(cur)
let save_et = &et
set et
exec cur . "retab"
let &et = save_et
let str = getline(cur)
let indent_str = indent_fix
let indent_n = strlen(indent_str)
" Shall we remember the current indentation
if indent < 0
let indent_orig = matchstr(str_orig, '^\s*')
if strlen(indent_orig) > 0
let indent_str = indent_orig
let indent_n = strlen(matchstr(str, '^\s*'))
endif
endif
" Trim trailing, leading and running whitespace
let str = substitute(str, '\s\+$', '', '')
let str = substitute(str, '^\s\+', '', '')
let str = substitute(str, '\s\+', ' ', 'g')
let str_n = strdisplaywidth(str)
" Possible addition of space after punctuation
if exists("join_str")
let str = substitute(str, join_str, '\1 ', 'g')
endif
let join_n = strdisplaywidth(str) - str_n
" Can extraspaces be added?
" Note that str_n may be less than strlen(str) [joinspaces above]
if strdisplaywidth(str) <= tw - indent_n && str_n > 0
" How many spaces should be added
let s_add = tw - str_n - indent_n - join_n
let s_nr = strlen(substitute(str, '\S', '', 'g') ) - join_n
let s_dup = s_add / s_nr
let s_mod = s_add % s_nr
" Test if the changed line fits with tw
if 0 <= (str_n + (maxspaces - 1)*s_nr + indent_n) - tw
" Duplicate spaces
while s_dup > 0
let str = substitute(str, '\( \+\)', ' \1', 'g')
let s_dup = s_dup - 1
endwhile
" Add extra spaces from the end
while s_mod > 0
let str = substitute(str, '\(\(\s\+\S\+\)\{' . s_mod . '}\)$', ' \1', '')
let s_mod = s_mod - 1
endwhile
" Indent the line
if indent_n > 0
let str = substitute(str, '^', indent_str, '' )
endif
" Replace the line
call setline(cur, str)
" Convert to whitespace
if &et == 0
exec cur . 'retab!'
endif
endif " Change of line
endif " Possible change
let cur = cur + 1
endwhile
norm ^
let &report = save_report
endfunction
" EOF vim: tw=78 ts=8 sw=4 sts=4 noet ai

View File

@@ -0,0 +1,784 @@
" matchit.vim: (global plugin) Extended "%" matching
" autload script of matchit plugin, see ../plugin/matchit.vim
" Last Change: May 20, 2024
" Neovim does not support scriptversion
if has("vimscript-4")
scriptversion 4
endif
let s:last_mps = ""
let s:last_words = ":"
let s:patBR = ""
let s:save_cpo = &cpo
set cpo&vim
" Auto-complete mappings: (not yet "ready for prime time")
" TODO Read :help write-plugin for the "right" way to let the user
" specify a key binding.
" let g:match_auto = '<C-]>'
" let g:match_autoCR = '<C-CR>'
" if exists("g:match_auto")
" execute "inoremap " . g:match_auto . ' x<Esc>"=<SID>Autocomplete()<CR>Pls'
" endif
" if exists("g:match_autoCR")
" execute "inoremap " . g:match_autoCR . ' <CR><C-R>=<SID>Autocomplete()<CR>'
" endif
" if exists("g:match_gthhoh")
" execute "inoremap " . g:match_gthhoh . ' <C-O>:call <SID>Gthhoh()<CR>'
" endif " gthhoh = "Get the heck out of here!"
let s:notslash = '\\\@1<!\%(\\\\\)*'
function s:RestoreOptions()
" In s:CleanUp(), :execute "set" restore_options .
let restore_options = ""
if get(b:, 'match_ignorecase', &ic) != &ic
let restore_options ..= (&ic ? " " : " no") .. "ignorecase"
let &ignorecase = b:match_ignorecase
endif
if &ve != ''
let restore_options = " ve=" .. &ve .. restore_options
set ve=
endif
if &smartcase
let restore_options = " smartcase " .. restore_options
set nosmartcase
endif
return restore_options
endfunction
function matchit#Match_wrapper(word, forward, mode) range
let restore_options = s:RestoreOptions()
" In s:CleanUp(), we may need to check whether the cursor moved forward.
let startpos = [line("."), col(".")]
" if a count has been applied, use the default [count]% mode (see :h N%)
if v:count
exe "normal! " .. v:count .. "%"
return s:CleanUp(restore_options, a:mode, startpos)
end
if a:mode =~# "v" && mode(1) =~# 'ni'
exe "norm! gv"
elseif a:mode == "o" && mode(1) !~# '[vV]'
exe "norm! v"
" If this function was called from Visual mode, make sure that the cursor
" is at the correct end of the Visual range:
elseif a:mode == "v"
execute "normal! gv\<Esc>"
let startpos = [line("."), col(".")]
endif
" First step: if not already done, set the script variables
" s:do_BR flag for whether there are backrefs
" s:pat parsed version of b:match_words
" s:all regexp based on s:pat and the default groups
if !exists("b:match_words") || b:match_words == ""
let match_words = ""
elseif b:match_words =~ ":"
let match_words = b:match_words
else
" Allow b:match_words = "GetVimMatchWords()" .
execute "let match_words =" b:match_words
endif
" Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion!
if (match_words != s:last_words) || (&mps != s:last_mps)
\ || exists("b:match_debug")
let s:last_mps = &mps
" quote the special chars in 'matchpairs', replace [,:] with \| and then
" append the builtin pairs (/*, */, #if, #ifdef, #ifndef, #else, #elif,
" #elifdef, #elifndef, #endif)
let default = escape(&mps, '[$^.*~\\/?]') .. (strlen(&mps) ? "," : "") ..
\ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\%(n\=def\)\=\>:#\s*endif\>'
" s:all = pattern with all the keywords
let match_words = match_words .. (strlen(match_words) ? "," : "") .. default
let s:last_words = match_words
if match_words !~ s:notslash .. '\\\d'
let s:do_BR = 0
let s:pat = match_words
else
let s:do_BR = 1
let s:pat = s:ParseWords(match_words)
endif
let s:all = substitute(s:pat, s:notslash .. '\zs[,:]\+', '\\|', 'g')
" un-escape \, to ,
let s:all = substitute(s:all, '\\,', ',', 'g')
" Just in case there are too many '\(...)' groups inside the pattern, make
" sure to use \%(...) groups, so that error E872 can be avoided
let s:all = substitute(s:all, '\\(', '\\%(', 'g')
let s:all = '\%(' .. s:all .. '\)'
if exists("b:match_debug")
let b:match_pat = s:pat
endif
" Reconstruct the version with unresolved backrefs.
let s:patBR = substitute(match_words .. ',',
\ s:notslash .. '\zs[,:]*,[,:]*', ',', 'g')
let s:patBR = substitute(s:patBR, s:notslash .. '\zs:\{2,}', ':', 'g')
" un-escape \, to ,
let s:patBR = substitute(s:patBR, '\\,', ',', 'g')
endif
" Second step: set the following local variables:
" matchline = line on which the cursor started
" curcol = number of characters before match
" prefix = regexp for start of line to start of match
" suffix = regexp for end of match to end of line
" Require match to end on or after the cursor and prefer it to
" start on or before the cursor.
let matchline = getline(startpos[0])
if a:word != ''
" word given
if a:word !~ s:all
echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE
return s:CleanUp(restore_options, a:mode, startpos)
endif
let matchline = a:word
let curcol = 0
let prefix = '^\%('
let suffix = '\)$'
" Now the case when "word" is not given
else " Find the match that ends on or after the cursor and set curcol.
let regexp = s:Wholematch(matchline, s:all, startpos[1]-1)
let curcol = match(matchline, regexp)
" If there is no match, give up.
if curcol == -1
return s:CleanUp(restore_options, a:mode, startpos)
endif
let endcol = matchend(matchline, regexp)
let suf = strlen(matchline) - endcol
let prefix = (curcol ? '^.*\%' .. (curcol + 1) .. 'c\%(' : '^\%(')
let suffix = (suf ? '\)\%' .. (endcol + 1) .. 'c.*$' : '\)$')
endif
if exists("b:match_debug")
let b:match_match = matchstr(matchline, regexp)
let b:match_col = curcol+1
endif
" Third step: Find the group and single word that match, and the original
" (backref) versions of these. Then, resolve the backrefs.
" Set the following local variable:
" group = colon-separated list of patterns, one of which matches
" = ini:mid:fin or ini:fin
"
" Now, set group and groupBR to the matching group: 'if:endif' or
" 'while:endwhile' or whatever. A bit of a kluge: s:Choose() returns
" group . "," . groupBR, and we pick it apart.
let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, s:patBR)
let i = matchend(group, s:notslash .. ",")
let groupBR = strpart(group, i)
let group = strpart(group, 0, i-1)
" Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
if s:do_BR " Do the hard part: resolve those backrefs!
let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
endif
if exists("b:match_debug")
let b:match_wholeBR = groupBR
let i = matchend(groupBR, s:notslash .. ":")
let b:match_iniBR = strpart(groupBR, 0, i-1)
endif
" Fourth step: Set the arguments for searchpair().
let i = matchend(group, s:notslash .. ":")
let j = matchend(group, '.*' .. s:notslash .. ":")
let ini = strpart(group, 0, i-1)
let mid = substitute(strpart(group, i,j-i-1), s:notslash .. '\zs:', '\\|', 'g')
let fin = strpart(group, j)
"Un-escape the remaining , and : characters.
let ini = substitute(ini, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g')
let mid = substitute(mid, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g')
let fin = substitute(fin, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g')
" searchpair() requires that these patterns avoid \(\) groups.
let ini = substitute(ini, s:notslash .. '\zs\\(', '\\%(', 'g')
let mid = substitute(mid, s:notslash .. '\zs\\(', '\\%(', 'g')
let fin = substitute(fin, s:notslash .. '\zs\\(', '\\%(', 'g')
" Set mid. This is optimized for readability, not micro-efficiency!
if a:forward && matchline =~ prefix .. fin .. suffix
\ || !a:forward && matchline =~ prefix .. ini .. suffix
let mid = ""
endif
" Set flag. This is optimized for readability, not micro-efficiency!
if a:forward && matchline =~ prefix .. fin .. suffix
\ || !a:forward && matchline !~ prefix .. ini .. suffix
let flag = "bW"
else
let flag = "W"
endif
" Set skip.
if exists("b:match_skip")
let skip = b:match_skip
elseif exists("b:match_comment") " backwards compatibility and testing!
let skip = "r:" .. b:match_comment
else
let skip = 's:comment\|string'
endif
let skip = s:ParseSkip(skip)
if exists("b:match_debug")
let b:match_ini = ini
let b:match_tail = (strlen(mid) ? mid .. '\|' : '') .. fin
endif
" Fifth step: actually start moving the cursor and call searchpair().
" Later, :execute restore_cursor to get to the original screen.
let view = winsaveview()
call cursor(0, curcol + 1)
if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
let skip = "0"
else
execute "if " .. skip .. "| let skip = '0' | endif"
endif
let sp_return = searchpair(ini, mid, fin, flag, skip)
if &selection isnot# 'inclusive' && a:mode == 'v'
" move cursor one pos to the right, because selection is not inclusive
" add virtualedit=onemore, to make it work even when the match ends the
" line
if !(col('.') < col('$')-1)
let eolmark=1 " flag to set a mark on eol (since we cannot move there)
endif
norm! l
endif
let final_position = "call cursor(" .. line(".") .. "," .. col(".") .. ")"
" Restore cursor position and original screen.
call winrestview(view)
normal! m'
if sp_return > 0
execute final_position
endif
if exists('eolmark') && eolmark
call setpos("''", [0, line('.'), col('$'), 0]) " set mark on the eol
endif
return s:CleanUp(restore_options, a:mode, startpos, mid .. '\|' .. fin)
endfun
" Restore options and do some special handling for Operator-pending mode.
" The optional argument is the tail of the matching group.
fun! s:CleanUp(options, mode, startpos, ...)
if strlen(a:options)
execute "set" a:options
endif
" Open folds, if appropriate.
if a:mode != "o"
if &foldopen =~ "percent"
normal! zv
endif
" In Operator-pending mode, we want to include the whole match
" (for example, d%).
" This is only a problem if we end up moving in the forward direction.
elseif (a:startpos[0] < line(".")) ||
\ (a:startpos[0] == line(".") && a:startpos[1] < col("."))
if a:0
" Check whether the match is a single character. If not, move to the
" end of the match.
let matchline = getline(".")
let currcol = col(".")
let regexp = s:Wholematch(matchline, a:1, currcol-1)
let endcol = matchend(matchline, regexp)
if endcol > currcol " This is NOT off by one!
call cursor(0, endcol)
endif
endif " a:0
endif " a:mode != "o" && etc.
return 0
endfun
" Example (simplified HTML patterns): if
" a:groupBR = '<\(\k\+\)>:</\1>'
" a:prefix = '^.\{3}\('
" a:group = '<\(\k\+\)>:</\(\k\+\)>'
" a:suffix = '\).\{2}$'
" a:matchline = "123<tag>12" or "123</tag>12"
" then extract "tag" from a:matchline and return "<tag>:</tag>" .
fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)
if a:matchline !~ a:prefix ..
\ substitute(a:group, s:notslash .. '\zs:', '\\|', 'g') .. a:suffix
return a:group
endif
let i = matchend(a:groupBR, s:notslash .. ':')
let ini = strpart(a:groupBR, 0, i-1)
let tailBR = strpart(a:groupBR, i)
let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,
\ a:groupBR)
let i = matchend(word, s:notslash .. ":")
let wordBR = strpart(word, i)
let word = strpart(word, 0, i-1)
" Now, a:matchline =~ a:prefix . word . a:suffix
if wordBR != ini
let table = s:Resolve(ini, wordBR, "table")
else
let table = ""
let d = 0
while d < 10
if tailBR =~ s:notslash .. '\\' .. d
let table = table .. d
else
let table = table .. "-"
endif
let d = d + 1
endwhile
endif
let d = 9
while d
if table[d] != "-"
let backref = substitute(a:matchline, a:prefix .. word .. a:suffix,
\ '\' .. table[d], "")
" Are there any other characters that should be escaped?
let backref = escape(backref, '*,:')
execute s:Ref(ini, d, "start", "len")
let ini = strpart(ini, 0, start) .. backref .. strpart(ini, start+len)
let tailBR = substitute(tailBR, s:notslash .. '\zs\\' .. d,
\ escape(backref, '\\&'), 'g')
endif
let d = d-1
endwhile
if exists("b:match_debug")
if s:do_BR
let b:match_table = table
let b:match_word = word
else
let b:match_table = ""
let b:match_word = ""
endif
endif
return ini .. ":" .. tailBR
endfun
" Input a comma-separated list of groups with backrefs, such as
" a:groups = '\(foo\):end\1,\(bar\):end\1'
" and return a comma-separated list of groups with backrefs replaced:
" return '\(foo\):end\(foo\),\(bar\):end\(bar\)'
fun! s:ParseWords(groups)
let groups = substitute(a:groups .. ",", s:notslash .. '\zs[,:]*,[,:]*', ',', 'g')
let groups = substitute(groups, s:notslash .. '\zs:\{2,}', ':', 'g')
let parsed = ""
while groups =~ '[^,:]'
let i = matchend(groups, s:notslash .. ':')
let j = matchend(groups, s:notslash .. ',')
let ini = strpart(groups, 0, i-1)
let tail = strpart(groups, i, j-i-1) .. ":"
let groups = strpart(groups, j)
let parsed = parsed .. ini
let i = matchend(tail, s:notslash .. ':')
while i != -1
" In 'if:else:endif', ini='if' and word='else' and then word='endif'.
let word = strpart(tail, 0, i-1)
let tail = strpart(tail, i)
let i = matchend(tail, s:notslash .. ':')
let parsed = parsed .. ":" .. s:Resolve(ini, word, "word")
endwhile " Now, tail has been used up.
let parsed = parsed .. ","
endwhile " groups =~ '[^,:]'
let parsed = substitute(parsed, ',$', '', '')
return parsed
endfun
" TODO I think this can be simplified and/or made more efficient.
" TODO What should I do if a:start is out of range?
" Return a regexp that matches all of a:string, such that
" matchstr(a:string, regexp) represents the match for a:pat that starts
" as close to a:start as possible, before being preferred to after, and
" ends after a:start .
" Usage:
" let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1)
" let i = match(getline("."), regexp)
" let j = matchend(getline("."), regexp)
" let match = matchstr(getline("."), regexp)
fun! s:Wholematch(string, pat, start)
let group = '\%(' .. a:pat .. '\)'
let prefix = (a:start ? '\(^.*\%<' .. (a:start + 2) .. 'c\)\zs' : '^')
let len = strlen(a:string)
let suffix = (a:start+1 < len ? '\(\%>' .. (a:start+1) .. 'c.*$\)\@=' : '$')
if a:string !~ prefix .. group .. suffix
let prefix = ''
endif
return prefix .. group .. suffix
endfun
" No extra arguments: s:Ref(string, d) will
" find the d'th occurrence of '\(' and return it, along with everything up
" to and including the matching '\)'.
" One argument: s:Ref(string, d, "start") returns the index of the start
" of the d'th '\(' and any other argument returns the length of the group.
" Two arguments: s:Ref(string, d, "foo", "bar") returns a string to be
" executed, having the effect of
" :let foo = s:Ref(string, d, "start")
" :let bar = s:Ref(string, d, "len")
fun! s:Ref(string, d, ...)
let len = strlen(a:string)
if a:d == 0
let start = 0
else
let cnt = a:d
let match = a:string
while cnt
let cnt = cnt - 1
let index = matchend(match, s:notslash .. '\\(')
if index == -1
return ""
endif
let match = strpart(match, index)
endwhile
let start = len - strlen(match)
if a:0 == 1 && a:1 == "start"
return start - 2
endif
let cnt = 1
while cnt
let index = matchend(match, s:notslash .. '\\(\|\\)') - 1
if index == -2
return ""
endif
" Increment if an open, decrement if a ')':
let cnt = cnt + (match[index]=="(" ? 1 : -1) " ')'
let match = strpart(match, index+1)
endwhile
let start = start - 2
let len = len - start - strlen(match)
endif
if a:0 == 1
return len
elseif a:0 == 2
return "let " .. a:1 .. "=" .. start .. "| let " .. a:2 .. "=" .. len
else
return strpart(a:string, start, len)
endif
endfun
" Count the number of disjoint copies of pattern in string.
" If the pattern is a literal string and contains no '0' or '1' characters
" then s:Count(string, pattern, '0', '1') should be faster than
" s:Count(string, pattern).
fun! s:Count(string, pattern, ...)
let pat = escape(a:pattern, '\\')
if a:0 > 1
let foo = substitute(a:string, '[^' .. a:pattern .. ']', "a:1", "g")
let foo = substitute(a:string, pat, a:2, "g")
let foo = substitute(foo, '[^' .. a:2 .. ']', "", "g")
return strlen(foo)
endif
let result = 0
let foo = a:string
let index = matchend(foo, pat)
while index != -1
let result = result + 1
let foo = strpart(foo, index)
let index = matchend(foo, pat)
endwhile
return result
endfun
" s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where
" word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'. That is, the first
" '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this
" indicates that all other instances of '\1' in target are to be replaced
" by '\3'. The hard part is dealing with nesting...
" Note that ":" is an illegal character for source and target,
" unless it is preceded by "\".
fun! s:Resolve(source, target, output)
let word = a:target
let i = matchend(word, s:notslash .. '\\\d') - 1
let table = "----------"
while i != -2 " There are back references to be replaced.
let d = word[i]
let backref = s:Ref(a:source, d)
" The idea is to replace '\d' with backref. Before we do this,
" replace any \(\) groups in backref with :1, :2, ... if they
" correspond to the first, second, ... group already inserted
" into backref. Later, replace :1 with \1 and so on. The group
" number w+b within backref corresponds to the group number
" s within a:source.
" w = number of '\(' in word before the current one
let w = s:Count(
\ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1')
let b = 1 " number of the current '\(' in backref
let s = d " number of the current '\(' in a:source
while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1')
\ && s < 10
if table[s] == "-"
if w + b < 10
" let table[s] = w + b
let table = strpart(table, 0, s) .. (w+b) .. strpart(table, s+1)
endif
let b = b + 1
let s = s + 1
else
execute s:Ref(backref, b, "start", "len")
let ref = strpart(backref, start, len)
let backref = strpart(backref, 0, start) .. ":" .. table[s]
\ .. strpart(backref, start+len)
let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1')
endif
endwhile
let word = strpart(word, 0, i-1) .. backref .. strpart(word, i+1)
let i = matchend(word, s:notslash .. '\\\d') - 1
endwhile
let word = substitute(word, s:notslash .. '\zs:', '\\', 'g')
if a:output == "table"
return table
elseif a:output == "word"
return word
else
return table .. word
endif
endfun
" Assume a:comma = ",". Then the format for a:patterns and a:1 is
" a:patterns = "<pat1>,<pat2>,..."
" a:1 = "<alt1>,<alt2>,..."
" If <patn> is the first pattern that matches a:string then return <patn>
" if no optional arguments are given; return <patn>,<altn> if a:1 is given.
fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...)
let tail = (a:patterns =~ a:comma .. "$" ? a:patterns : a:patterns .. a:comma)
let i = matchend(tail, s:notslash .. a:comma)
if a:0
let alttail = (a:1 =~ a:comma .. "$" ? a:1 : a:1 .. a:comma)
let j = matchend(alttail, s:notslash .. a:comma)
endif
let current = strpart(tail, 0, i-1)
if a:branch == ""
let currpat = current
else
let currpat = substitute(current, s:notslash .. a:branch, '\\|', 'g')
endif
" un-escape \, to ,
let currpat = substitute(currpat, '\\,', ',', 'g')
while a:string !~ a:prefix .. currpat .. a:suffix
let tail = strpart(tail, i)
let i = matchend(tail, s:notslash .. a:comma)
if i == -1
return -1
endif
let current = strpart(tail, 0, i-1)
if a:branch == ""
let currpat = current
else
let currpat = substitute(current, s:notslash .. a:branch, '\\|', 'g')
endif
if a:0
let alttail = strpart(alttail, j)
let j = matchend(alttail, s:notslash .. a:comma)
endif
endwhile
if a:0
let current = current .. a:comma .. strpart(alttail, 0, j-1)
endif
return current
endfun
fun! matchit#Match_debug()
let b:match_debug = 1 " Save debugging information.
" pat = all of b:match_words with backrefs parsed
amenu &Matchit.&pat :echo b:match_pat<CR>
" match = bit of text that is recognized as a match
amenu &Matchit.&match :echo b:match_match<CR>
" curcol = cursor column of the start of the matching text
amenu &Matchit.&curcol :echo b:match_col<CR>
" wholeBR = matching group, original version
amenu &Matchit.wh&oleBR :echo b:match_wholeBR<CR>
" iniBR = 'if' piece, original version
amenu &Matchit.ini&BR :echo b:match_iniBR<CR>
" ini = 'if' piece, with all backrefs resolved from match
amenu &Matchit.&ini :echo b:match_ini<CR>
" tail = 'else\|endif' piece, with all backrefs resolved from match
amenu &Matchit.&tail :echo b:match_tail<CR>
" fin = 'endif' piece, with all backrefs resolved from match
amenu &Matchit.&word :echo b:match_word<CR>
" '\'.d in ini refers to the same thing as '\'.table[d] in word.
amenu &Matchit.t&able :echo '0:' .. b:match_table .. ':9'<CR>
endfun
" Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW"
" or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W".
" Return a "mark" for the original position, so that
" let m = MultiMatch("bW", "n") ... call winrestview(m)
" will return to the original position. If there is a problem, do not
" move the cursor and return {}, unless a count is given, in which case
" go up or down as many levels as possible and again return {}.
" TODO This relies on the same patterns as % matching. It might be a good
" idea to give it its own matching patterns.
fun! matchit#MultiMatch(spflag, mode)
let restore_options = s:RestoreOptions()
let startpos = [line("."), col(".")]
" save v:count1 variable, might be reset from the restore_cursor command
let level = v:count1
if a:mode == "o" && mode(1) !~# '[vV]'
exe "norm! v"
endif
" First step: if not already done, set the script variables
" s:do_BR flag for whether there are backrefs
" s:pat parsed version of b:match_words
" s:all regexp based on s:pat and the default groups
" This part is copied and slightly modified from matchit#Match_wrapper().
if !exists("b:match_words") || b:match_words == ""
let match_words = ""
" Allow b:match_words = "GetVimMatchWords()" .
elseif b:match_words =~ ":"
let match_words = b:match_words
else
execute "let match_words =" b:match_words
endif
if (match_words != s:last_words) || (&mps != s:last_mps) ||
\ exists("b:match_debug")
let default = escape(&mps, '[$^.*~\\/?]') .. (strlen(&mps) ? "," : "") ..
\ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>'
let s:last_mps = &mps
let match_words = match_words .. (strlen(match_words) ? "," : "") .. default
let s:last_words = match_words
if match_words !~ s:notslash .. '\\\d'
let s:do_BR = 0
let s:pat = match_words
else
let s:do_BR = 1
let s:pat = s:ParseWords(match_words)
endif
let s:all = '\%(' .. substitute(s:pat, '[,:]\+', '\\|', 'g') .. '\)'
if exists("b:match_debug")
let b:match_pat = s:pat
endif
" Reconstruct the version with unresolved backrefs.
let s:patBR = substitute(match_words .. ',',
\ s:notslash .. '\zs[,:]*,[,:]*', ',', 'g')
let s:patBR = substitute(s:patBR, s:notslash .. '\zs:\{2,}', ':', 'g')
endif
" Second step: figure out the patterns for searchpair()
" and save the screen, cursor position, and 'ignorecase'.
" - TODO: A lot of this is copied from matchit#Match_wrapper().
" - maybe even more functionality should be split off
" - into separate functions!
let openlist = split(s:pat .. ',', s:notslash .. '\zs:.\{-}' .. s:notslash .. ',')
let midclolist = split(',' .. s:pat, s:notslash .. '\zs,.\{-}' .. s:notslash .. ':')
call map(midclolist, {-> split(v:val, s:notslash .. ':')})
let closelist = []
let middlelist = []
call map(midclolist, {i,v -> [extend(closelist, v[-1 : -1]),
\ extend(middlelist, v[0 : -2])]})
call map(openlist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v})
call map(middlelist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v})
call map(closelist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v})
let open = join(openlist, ',')
let middle = join(middlelist, ',')
let close = join(closelist, ',')
if exists("b:match_skip")
let skip = b:match_skip
elseif exists("b:match_comment") " backwards compatibility and testing!
let skip = "r:" .. b:match_comment
else
let skip = 's:comment\|string'
endif
let skip = s:ParseSkip(skip)
let view = winsaveview()
" Third step: call searchpair().
" Replace '\('--but not '\\('--with '\%(' and ',' with '\|'.
let openpat = substitute(open, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g')
let openpat = substitute(openpat, ',', '\\|', 'g')
let closepat = substitute(close, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g')
let closepat = substitute(closepat, ',', '\\|', 'g')
let middlepat = substitute(middle, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g')
let middlepat = substitute(middlepat, ',', '\\|', 'g')
if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
let skip = '0'
else
try
execute "if " .. skip .. "| let skip = '0' | endif"
catch /^Vim\%((\a\+)\)\=:E363/
" We won't find anything, so skip searching, should keep Vim responsive.
return {}
endtry
endif
mark '
while level
if searchpair(openpat, middlepat, closepat, a:spflag, skip) < 1
call s:CleanUp(restore_options, a:mode, startpos)
return {}
endif
let level = level - 1
endwhile
" Restore options and return a string to restore the original position.
call s:CleanUp(restore_options, a:mode, startpos)
return view
endfun
" Search backwards for "if" or "while" or "<tag>" or ...
" and return "endif" or "endwhile" or "</tag>" or ... .
" For now, this uses b:match_words and the same script variables
" as matchit#Match_wrapper() . Later, it may get its own patterns,
" either from a buffer variable or passed as arguments.
" fun! s:Autocomplete()
" echo "autocomplete not yet implemented :-("
" if !exists("b:match_words") || b:match_words == ""
" return ""
" end
" let startpos = matchit#MultiMatch("bW")
"
" if startpos == ""
" return ""
" endif
" " - TODO: figure out whether 'if' or '<tag>' matched, and construct
" " - the appropriate closing.
" let matchline = getline(".")
" let curcol = col(".") - 1
" " - TODO: Change the s:all argument if there is a new set of match pats.
" let regexp = s:Wholematch(matchline, s:all, curcol)
" let suf = strlen(matchline) - matchend(matchline, regexp)
" let prefix = (curcol ? '^.\{' . curcol . '}\%(' : '^\%(')
" let suffix = (suf ? '\).\{' . suf . '}$' : '\)$')
" " Reconstruct the version with unresolved backrefs.
" let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g')
" let patBR = substitute(patBR, ':\{2,}', ':', "g")
" " Now, set group and groupBR to the matching group: 'if:endif' or
" " 'while:endwhile' or whatever.
" let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
" let i = matchend(group, s:notslash . ",")
" let groupBR = strpart(group, i)
" let group = strpart(group, 0, i-1)
" " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
" if s:do_BR
" let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
" endif
" " let g:group = group
"
" " - TODO: Construct the closing from group.
" let fake = "end" . expand("<cword>")
" execute startpos
" return fake
" endfun
" Close all open structures. "Get the heck out of here!"
" fun! s:Gthhoh()
" let close = s:Autocomplete()
" while strlen(close)
" put=close
" let close = s:Autocomplete()
" endwhile
" endfun
" Parse special strings as typical skip arguments for searchpair():
" s:foo becomes (current syntax item) =~ foo
" S:foo becomes (current syntax item) !~ foo
" r:foo becomes (line before cursor) =~ foo
" R:foo becomes (line before cursor) !~ foo
fun! s:ParseSkip(str)
let skip = a:str
if skip[1] == ":"
if skip[0] ==# "s"
let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" ..
\ strpart(skip,2) .. "'"
elseif skip[0] ==# "S"
let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" ..
\ strpart(skip,2) .. "'"
elseif skip[0] ==# "r"
let skip = "strpart(getline('.'),0,col('.'))=~'" .. strpart(skip,2) .. "'"
elseif skip[0] ==# "R"
let skip = "strpart(getline('.'),0,col('.'))!~'" .. strpart(skip,2) .. "'"
endif
endif
return skip
endfun
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:sts=2:sw=2:et:

View File

@@ -0,0 +1,412 @@
*matchit.txt* Extended "%" matching
For instructions on installing this file, type
`:help matchit-install`
inside Vim.
For Vim version 9.1. Last change: 2024 May 20
VIM REFERENCE MANUAL by Benji Fisher et al
*matchit* *matchit.vim*
1. Extended matching with "%" |matchit-intro|
2. Activation |matchit-activate|
3. Configuration |matchit-configure|
4. Supporting a New Language |matchit-newlang|
5. Known Bugs and Limitations |matchit-bugs|
The functionality mentioned here is a plugin, see |add-plugin|.
This plugin is only available if 'compatible' is not set.
==============================================================================
1. Extended matching with "%" *matchit-intro*
*matchit-%*
% Cycle forward through matching groups, such as "if", "else", "endif",
as specified by |b:match_words|.
*g%* *v_g%* *o_g%*
g% Cycle backwards through matching groups, as specified by
|b:match_words|. For example, go from "if" to "endif" to "else".
*[%* *v_[%* *o_[%*
[% Go to [count] previous unmatched group, as specified by
|b:match_words|. Similar to |[{|.
*]%* *v_]%* *o_]%*
]% Go to [count] next unmatched group, as specified by
|b:match_words|. Similar to |]}|.
*v_a%*
a% In Visual mode, select the matching group, as specified by
|b:match_words|, containing the cursor. Similar to |v_a[|.
A [count] is ignored, and only the first character of the closing
pattern is selected.
In Vim, as in plain vi, the percent key, |%|, jumps the cursor from a brace,
bracket, or paren to its match. This can be configured with the 'matchpairs'
option. The matchit plugin extends this in several ways:
You can match whole words, such as "if" and "endif", not just
single characters. You can also specify a |regular-expression|.
You can define groups with more than two words, such as "if",
"else", "endif". Banging on the "%" key will cycle from the "if" to
the first "else", the next "else", ..., the closing "endif", and back
to the opening "if". Nested structures are skipped. Using |g%| goes
in the reverse direction.
By default, words inside comments and strings are ignored, unless
the cursor is inside a comment or string when you type "%". If the
only thing you want to do is modify the behavior of "%" so that it
behaves this way, you do not have to define |b:match_words|, since the
script uses the 'matchpairs' option as well as this variable.
See |matchit-details| for details on what the script does, and |b:match_words|
for how to specify matching patterns.
MODES: *matchit-modes* *matchit-v_%* *matchit-o_%*
Mostly, % and related motions (|g%| and |[%| and |]%|) should just work like built-in
|motion| commands in |Operator-pending| and |Visual| modes (as of 8.1.648)
LANGUAGES: *matchit-languages*
Currently, the following languages are supported: Ada, ASP with VBS, Csh,
DTD, Entity, Essbase, Fortran, HTML, JSP (same as HTML), LaTeX, Lua, Pascal,
SGML, Shell, Tcsh, Vim, XML. Other languages may already have support via
the default |filetype-plugin|s in the standard vim distribution.
To support a new language, see |matchit-newlang| below.
DETAILS: *matchit-details* *matchit-parse*
Here is an outline of what matchit.vim does each time you hit the "%" key. If
there are |backref|s in |b:match_words| then the first step is to produce a
version in which these back references have been eliminated; if there are no
|backref|s then this step is skipped. This step is called parsing. For
example, "\(foo\|bar\):end\1" is parsed to yield
"\(foo\|bar\):end\(foo\|bar\)". This can get tricky, especially if there are
nested groups. If debugging is turned on, the parsed version is saved as
|b:match_pat|.
*matchit-choose*
Next, the script looks for a word on the current line that matches the pattern
just constructed. It includes the patterns from the 'matchpairs' option.
The goal is to do what you expect, which turns out to be a little complicated.
The script follows these rules:
Insist on a match that ends on or after the cursor.
Prefer a match that includes the cursor position (that is, one that
starts on or before the cursor).
Prefer a match that starts as close to the cursor as possible.
If more than one pattern in |b:match_words| matches, choose the one
that is listed first.
Examples:
Suppose you >
:let b:match_words = '<:>,<tag>:</tag>'
< and hit "%" with the cursor on or before the "<" in "a <tag> is born".
The pattern '<' comes first, so it is preferred over '<tag>', which
also matches. If the cursor is on the "t", however, then '<tag>' is
preferred, because this matches a bit of text containing the cursor.
If the two groups of patterns were reversed then '<' would never be
preferred.
Suppose you >
:let b:match_words = 'if:end if'
< (Note the space!) and hit "%" with the cursor at the end of "end if".
Then "if" matches, which is probably not what you want, but if the
cursor starts on the "end " then "end if" is chosen. (You can avoid
this problem by using a more complicated pattern.)
If there is no match, the cursor does not move. (Before version 1.13 of the
script, it would fall back on the usual behavior of |%|). If debugging is
turned on, the matched bit of text is saved as |b:match_match| and the cursor
column of the start of the match is saved as |b:match_col|.
Next, the script looks through |b:match_words| (original and parsed versions)
for the group and pattern that match. If debugging is turned on, the group is
saved as |b:match_ini| (the first pattern) and |b:match_tail| (the rest). If
there are |backref|s then, in addition, the matching pattern is saved as
|b:match_word| and a table of translations is saved as |b:match_table|. If
there are |backref|s, these are determined from the matching pattern and
|b:match_match| and substituted into each pattern in the matching group.
The script decides whether to search forwards or backwards and chooses
arguments for the |searchpair()| function. Then, the cursor is moved to the
start of the match, and |searchpair()| is called. By default, matching
structures inside strings and comments are ignored. This can be changed by
setting |b:match_skip|.
==============================================================================
2. Activation *matchit-activate*
To use the matchit plugin add this line to your |vimrc|: >
packadd! matchit
The script should start working the next time you start Vim.
To use the matchit plugin after Vim has started, execute this command: >
packadd matchit
(Earlier versions of the script did nothing unless a |buffer-variable| named
|b:match_words| was defined. Even earlier versions contained autocommands
that set this variable for various file types. Now, |b:match_words| is
defined in many of the default |filetype-plugin|s instead.)
For a new language, you can add autocommands to the script or to your vimrc
file, but the recommended method is to add a line such as >
let b:match_words = '\<foo\>:\<bar\>'
to the |filetype-plugin| for your language. See |b:match_words| below for how
this variable is interpreted.
TROUBLESHOOTING *matchit-troubleshoot*
The script should work in most installations of Vim. It may not work if Vim
was compiled with a minimal feature set, for example if the |+syntax| option
was not enabled. If your Vim has support for syntax compiled in, but you do
not have |syntax| highlighting turned on, matchit.vim should work, but it may
fail to skip matching groups in comments and strings. If the |filetype|
mechanism is turned off, the |b:match_words| variable will probably not be
defined automatically.
2.1 Temporarily disable the matchit plugin *matchit-disable* *:MatchDisable*
To temporarily disable the matchit plugin, after it hat been loaded,
execute this command: >
:MatchDisable
This will delete all the defined key mappings to the Vim default.
Now the "%" command will work like before loading the plugin |%|
2.2 Re-enable the matchit plugin *:MatchEnable*
To re-enable the plugin, after it was disabled, use the following command: >
:MatchEnable
This will resetup the key mappings.
==============================================================================
3. Configuration *matchit-configure*
There are several variables that govern the behavior of matchit.vim. Note
that these are variables local to the buffer, not options, so use |:let| to
define them, not |:set|. Some of these variables have values that matter; for
others, it only matters whether the variable has been defined. All of these
can be defined in the |filetype-plugin| or autocommand that defines
|b:match_words| or "on the fly."
The main variable is |b:match_words|. It is described in the section below on
supporting a new language.
*MatchError* *matchit-hl* *matchit-highlight*
MatchError is the highlight group for error messages from the script. By
default, it is linked to WarningMsg. If you do not want to be bothered by
error messages, you can define this to be something invisible. For example,
if you use the GUI version of Vim and your command line is normally white, you
can do >
:hi MatchError guifg=white guibg=white
<
*b:match_ignorecase*
If you >
:let b:match_ignorecase = 1
then matchit.vim acts as if 'ignorecase' is set: for example, "end" and "END"
are equivalent. If you >
:let b:match_ignorecase = 0
then matchit.vim treats "end" and "END" differently. (There will be no
b:match_infercase option unless someone requests it.)
*b:match_debug*
Define b:match_debug if you want debugging information to be saved. See
|matchit-debug|, below.
*b:match_skip*
If b:match_skip is defined, it is passed as the skip argument to
|searchpair()|. This controls when matching structures are skipped, or
ignored. By default, they are ignored inside comments and strings, as
determined by the |syntax| mechanism. (If syntax highlighting is turned off,
nothing is skipped.) You can set b:match_skip to a string, which evaluates to
a non-zero, numerical value if the match is to be skipped or zero if the match
should not be skipped. In addition, the following special values are
supported by matchit.vim:
s:foo becomes (current syntax item) =~ foo
S:foo becomes (current syntax item) !~ foo
r:foo becomes (line before cursor) =~ foo
R:foo becomes (line before cursor) !~ foo
(The "s" is meant to suggest "syntax", and the "r" is meant to suggest
"regular expression".)
Examples:
You can get the default behavior with >
:let b:match_skip = 's:comment\|string'
<
If you want to skip matching structures unless they are at the start
of the line (ignoring whitespace) then you can >
:let b:match_skip = 'R:^\s*'
< Do not do this if strings or comments can span several lines, since
the normal syntax checking will not be done if you set b:match_skip.
In LaTeX, since "%" is used as the comment character, you can >
:let b:match_skip = 'r:%'
< Unfortunately, this will skip anything after "\%", an escaped "%". To
allow for this, and also "\\%" (an escaped backslash followed by the
comment character) you can >
:let b:match_skip = 'r:\(^\|[^\\]\)\(\\\\\)*%'
<
See the $VIMRUNTIME/ftplugin/vim.vim for an example that uses both
syntax and a regular expression.
==============================================================================
4. Supporting a New Language *matchit-newlang*
*b:match_words*
In order for matchit.vim to support a new language, you must define a suitable
pattern for |b:match_words|. You may also want to set some of the
|matchit-configure| variables, as described above. If your language has a
complicated syntax, or many keywords, you will need to know something about
Vim's |regular-expression|s.
The format for |b:match_words| is similar to that of the 'matchpairs' option:
it is a comma (,)-separated list of groups; each group is a colon(:)-separated
list of patterns (regular expressions). Commas and backslashes that are part
of a pattern should be escaped with backslashes ('\:' and '\,'). It is OK to
have only one group; the effect is undefined if a group has only one pattern.
A simple example is >
:let b:match_words = '\<if\>:\<endif\>,'
\ . '\<while\>:\<continue\>:\<break\>:\<endwhile\>'
(In Vim regular expressions, |\<| and |\>| denote word boundaries. Thus "if"
matches the end of "endif" but "\<if\>" does not.) Then banging on the "%"
key will bounce the cursor between "if" and the matching "endif"; and from
"while" to any matching "continue" or "break", then to the matching "endwhile"
and back to the "while". It is almost always easier to use |literal-string|s
(single quotes) as above: '\<if\>' rather than "\\<if\\>" and so on.
Exception: If the ":" character does not appear in b:match_words, then it is
treated as an expression to be evaluated. For example, >
:let b:match_words = 'GetMatchWords()'
allows you to define a function. This can return a different string depending
on the current syntax, for example.
Once you have defined the appropriate value of |b:match_words|, you will
probably want to have this set automatically each time you edit the
appropriate file type. The recommended way to do this is by adding the
definition to a |filetype-plugin| file.
Tips: Be careful that your initial pattern does not match your final pattern.
See the example above for the use of word-boundary expressions. It is usually
better to use ".\{-}" (as many as necessary) instead of ".*" (as many as
possible). See |\{-|. For example, in the string "<tag>label</tag>", "<.*>"
matches the whole string whereas "<.\{-}>" and "<[^>]*>" match "<tag>" and
"</tag>".
*matchit-spaces* *matchit-s:notend*
If "if" is to be paired with "end if" (Note the space!) then word boundaries
are not enough. Instead, define a regular expression s:notend that will match
anything but "end" and use it as follows: >
:let s:notend = '\%(\<end\s\+\)\@<!'
:let b:match_words = s:notend . '\<if\>:\<end\s\+if\>'
< *matchit-s:sol*
This is a simplified version of what is done for Ada. The s:notend is a
|script-variable|. Similarly, you may want to define a start-of-line regular
expression >
:let s:sol = '\%(^\|;\)\s*'
if keywords are only recognized after the start of a line or after a
semicolon (;), with optional white space.
*matchit-backref* *matchit-\1*
In any group, the expressions |\1|, |\2|, ..., |\9| refer to parts of the
INITIAL pattern enclosed in |\(|escaped parentheses|\)|. These are referred
to as back references, or backrefs. For example, >
:let b:match_words = '\<b\(o\+\)\>:\(h\)\1\>'
means that "bo" pairs with "ho" and "boo" pairs with "hoo" and so on. Note
that "\1" does not refer to the "\(h\)" in this example. If you have
"\(nested \(parentheses\)\) then "\d" refers to the d-th "\(" and everything
up to and including the matching "\)": in "\(nested\(parentheses\)\)", "\1"
refers to everything and "\2" refers to "\(parentheses\)". If you use a
variable such as |s:notend| or |s:sol| in the previous paragraph then remember
to count any "\(" patterns in this variable. You do not have to count groups
defined by |\%(\)|.
It should be possible to resolve back references from any pattern in the
group. For example, >
:let b:match_words = '\(foo\)\(bar\):more\1:and\2:end\1\2'
would not work because "\2" cannot be determined from "morefoo" and "\1"
cannot be determined from "andbar". On the other hand, >
:let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'
should work (and have the same effect as "foobar:barfoo:endfoobar"), although
this has not been thoroughly tested.
You can use |zero-width| patterns such as |\@<=| and |\zs|. (The latter has
not been thoroughly tested in matchit.vim.) For example, if the keyword "if"
must occur at the start of the line, with optional white space, you might use
the pattern "\(^\s*\)\@<=if" so that the cursor will end on the "i" instead of
at the start of the line. For another example, if HTML had only one tag then
one could >
:let b:match_words = '<:>,<\@<=tag>:<\@<=/tag>'
so that "%" can bounce between matching "<" and ">" pairs or (starting on
"tag" or "/tag") between matching tags. Without the |\@<=|, the script would
bounce from "tag" to the "<" in "</tag>", and another "%" would not take you
back to where you started.
DEBUGGING *matchit-debug* *:MatchDebug*
If you are having trouble figuring out the appropriate definition of
|b:match_words| then you can take advantage of the same information I use when
debugging the script. This is especially true if you are not sure whether
your patterns or my script are at fault! To make this more convenient, I have
made the command :MatchDebug, which defines the variable |b:match_debug| and
creates a Matchit menu. This menu makes it convenient to check the values of
the variables described below. You will probably also want to read
|matchit-details| above.
Defining the variable |b:match_debug| causes the script to set the following
variables, each time you hit the "%" key. Several of these are only defined
if |b:match_words| includes |backref|s.
*b:match_pat*
The b:match_pat variable is set to |b:match_words| with |backref|s parsed.
*b:match_match*
The b:match_match variable is set to the bit of text that is recognized as a
match.
*b:match_col*
The b:match_col variable is set to the cursor column of the start of the
matching text.
*b:match_wholeBR*
The b:match_wholeBR variable is set to the comma-separated group of patterns
that matches, with |backref|s unparsed.
*b:match_iniBR*
The b:match_iniBR variable is set to the first pattern in |b:match_wholeBR|.
*b:match_ini*
The b:match_ini variable is set to the first pattern in |b:match_wholeBR|,
with |backref|s resolved from |b:match_match|.
*b:match_tail*
The b:match_tail variable is set to the remaining patterns in
|b:match_wholeBR|, with |backref|s resolved from |b:match_match|.
*b:match_word*
The b:match_word variable is set to the pattern from |b:match_wholeBR| that
matches |b:match_match|.
*b:match_table*
The back reference '\'.d refers to the same thing as '\'.b:match_table[d] in
|b:match_word|.
==============================================================================
5. Known Bugs and Limitations *matchit-bugs*
Repository: https://github.com/chrisbra/matchit/
Bugs can be reported at the repository and the latest development snapshot can
also be downloaded there.
Just because I know about a bug does not mean that it is on my todo list. I
try to respond to reports of bugs that cause real problems. If it does not
cause serious problems, or if there is a work-around, a bug may sit there for
a while. Moral: if a bug (known or not) bothers you, let me know.
It would be nice if "\0" were recognized as the entire pattern. That is, it
would be nice if "foo:\end\0" had the same effect as "\(foo\):\end\1". I may
try to implement this in a future version. (This is not so easy to arrange as
you might think!)
==============================================================================
vim:tw=78:ts=8:fo=tcq2:ft=help:

View File

@@ -0,0 +1,53 @@
:MatchDebug matchit.txt /*:MatchDebug*
:MatchDisable matchit.txt /*:MatchDisable*
:MatchEnable matchit.txt /*:MatchEnable*
MatchError matchit.txt /*MatchError*
[% matchit.txt /*[%*
]% matchit.txt /*]%*
b:match_col matchit.txt /*b:match_col*
b:match_debug matchit.txt /*b:match_debug*
b:match_ignorecase matchit.txt /*b:match_ignorecase*
b:match_ini matchit.txt /*b:match_ini*
b:match_iniBR matchit.txt /*b:match_iniBR*
b:match_match matchit.txt /*b:match_match*
b:match_pat matchit.txt /*b:match_pat*
b:match_skip matchit.txt /*b:match_skip*
b:match_table matchit.txt /*b:match_table*
b:match_tail matchit.txt /*b:match_tail*
b:match_wholeBR matchit.txt /*b:match_wholeBR*
b:match_word matchit.txt /*b:match_word*
b:match_words matchit.txt /*b:match_words*
g% matchit.txt /*g%*
matchit matchit.txt /*matchit*
matchit-% matchit.txt /*matchit-%*
matchit-\1 matchit.txt /*matchit-\\1*
matchit-activate matchit.txt /*matchit-activate*
matchit-backref matchit.txt /*matchit-backref*
matchit-bugs matchit.txt /*matchit-bugs*
matchit-choose matchit.txt /*matchit-choose*
matchit-configure matchit.txt /*matchit-configure*
matchit-debug matchit.txt /*matchit-debug*
matchit-details matchit.txt /*matchit-details*
matchit-disable matchit.txt /*matchit-disable*
matchit-highlight matchit.txt /*matchit-highlight*
matchit-hl matchit.txt /*matchit-hl*
matchit-intro matchit.txt /*matchit-intro*
matchit-languages matchit.txt /*matchit-languages*
matchit-modes matchit.txt /*matchit-modes*
matchit-newlang matchit.txt /*matchit-newlang*
matchit-o_% matchit.txt /*matchit-o_%*
matchit-parse matchit.txt /*matchit-parse*
matchit-s:notend matchit.txt /*matchit-s:notend*
matchit-s:sol matchit.txt /*matchit-s:sol*
matchit-spaces matchit.txt /*matchit-spaces*
matchit-troubleshoot matchit.txt /*matchit-troubleshoot*
matchit-v_% matchit.txt /*matchit-v_%*
matchit.txt matchit.txt /*matchit.txt*
matchit.vim matchit.txt /*matchit.vim*
o_[% matchit.txt /*o_[%*
o_]% matchit.txt /*o_]%*
o_g% matchit.txt /*o_g%*
v_[% matchit.txt /*v_[%*
v_]% matchit.txt /*v_]%*
v_a% matchit.txt /*v_a%*
v_g% matchit.txt /*v_g%*

View File

@@ -0,0 +1,127 @@
" matchit.vim: (global plugin) Extended "%" matching
" Maintainer: Christian Brabandt
" Version: 1.20
" Last Change: 2024 May 20
" Repository: https://github.com/chrisbra/matchit
" Previous URL:http://www.vim.org/script.php?script_id=39
" Previous Maintainer: Benji Fisher PhD <benji@member.AMS.org>
" Documentation:
" The documentation is in a separate file: ../doc/matchit.txt
" Credits:
" Vim editor by Bram Moolenaar (Thanks, Bram!)
" Original script and design by Raul Segura Acevedo
" Support for comments by Douglas Potts
" Support for back references and other improvements by Benji Fisher
" Support for many languages by Johannes Zellner
" Suggestions for improvement, bug reports, and support for additional
" languages by Jordi-Albert Batalla, Neil Bird, Servatius Brandt, Mark
" Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner.
" Debugging:
" If you'd like to try the built-in debugging commands...
" :MatchDebug to activate debugging for the current buffer
" This saves the values of several key script variables as buffer-local
" variables. See the MatchDebug() function, below, for details.
" TODO: I should think about multi-line patterns for b:match_words.
" This would require an option: how many lines to scan (default 1).
" This would be useful for Python, maybe also for *ML.
" TODO: Maybe I should add a menu so that people will actually use some of
" the features that I have implemented.
" TODO: Eliminate the MultiMatch function. Add yet another argument to
" Match_wrapper() instead.
" TODO: Allow :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'
" TODO: Make backrefs safer by using '\V' (very no-magic).
" TODO: Add a level of indirection, so that custom % scripts can use my
" work but extend it.
" Allow user to prevent loading and prevent duplicate loading.
if exists("g:loaded_matchit") || &cp
finish
endif
let g:loaded_matchit = 1
let s:save_cpo = &cpo
set cpo&vim
fun MatchEnable()
nnoremap <silent> <Plug>(MatchitNormalForward) :<C-U>call matchit#Match_wrapper('',1,'n')<CR>
nnoremap <silent> <Plug>(MatchitNormalBackward) :<C-U>call matchit#Match_wrapper('',0,'n')<CR>
xnoremap <silent> <Plug>(MatchitVisualForward) :<C-U>call matchit#Match_wrapper('',1,'v')<CR>
\:if col("''") != col("$") \| exe ":normal! m'" \| endif<cr>gv``
xnoremap <silent> <Plug>(MatchitVisualBackward) :<C-U>call matchit#Match_wrapper('',0,'v')<CR>m'gv``
onoremap <silent> <Plug>(MatchitOperationForward) :<C-U>call matchit#Match_wrapper('',1,'o')<CR>
onoremap <silent> <Plug>(MatchitOperationBackward) :<C-U>call matchit#Match_wrapper('',0,'o')<CR>
" Analogues of [{ and ]} using matching patterns:
nnoremap <silent> <Plug>(MatchitNormalMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR>
nnoremap <silent> <Plug>(MatchitNormalMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR>
xnoremap <silent> <Plug>(MatchitVisualMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR>m'gv``
xnoremap <silent> <Plug>(MatchitVisualMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR>m'gv``
onoremap <silent> <Plug>(MatchitOperationMultiBackward) :<C-U>call matchit#MultiMatch("bW", "o")<CR>
onoremap <silent> <Plug>(MatchitOperationMultiForward) :<C-U>call matchit#MultiMatch("W", "o")<CR>
" text object:
xmap <silent> <Plug>(MatchitVisualTextObject) <Plug>(MatchitVisualMultiBackward)o<Plug>(MatchitVisualMultiForward)
if !exists("g:no_plugin_maps")
nmap <silent> % <Plug>(MatchitNormalForward)
nmap <silent> g% <Plug>(MatchitNormalBackward)
xmap <silent> % <Plug>(MatchitVisualForward)
xmap <silent> g% <Plug>(MatchitVisualBackward)
omap <silent> % <Plug>(MatchitOperationForward)
omap <silent> g% <Plug>(MatchitOperationBackward)
" Analogues of [{ and ]} using matching patterns:
nmap <silent> [% <Plug>(MatchitNormalMultiBackward)
nmap <silent> ]% <Plug>(MatchitNormalMultiForward)
xmap <silent> [% <Plug>(MatchitVisualMultiBackward)
xmap <silent> ]% <Plug>(MatchitVisualMultiForward)
omap <silent> [% <Plug>(MatchitOperationMultiBackward)
omap <silent> ]% <Plug>(MatchitOperationMultiForward)
" Text object
xmap a% <Plug>(MatchitVisualTextObject)
endif
endfun
fun MatchDisable()
" remove all the setup keymappings
nunmap %
nunmap g%
xunmap %
xunmap g%
ounmap %
ounmap g%
nunmap [%
nunmap ]%
xunmap [%
xunmap ]%
ounmap [%
ounmap ]%
xunmap a%
endfun
" Call this function to turn on debugging information. Every time the main
" script is run, buffer variables will be saved. These can be used directly
" or viewed using the menu items below.
if !exists(":MatchDebug")
command! -nargs=0 MatchDebug call matchit#Match_debug()
endif
if !exists(":MatchDisable")
command! -nargs=0 MatchDisable :call MatchDisable()
endif
if !exists(":MatchEnable")
command! -nargs=0 MatchEnable :call MatchEnable()
endif
call MatchEnable()
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:sts=2:sw=2:et:

View File

@@ -0,0 +1,20 @@
" nohlsearch.vim: Auto turn off hlsearch
" Last Change: 2024-07-31
" Maintainer: Maxim Kim <habamax@gmail.com>
"
" turn off hlsearch after:
" - doing nothing for 'updatetime'
" - getting into insert mode
if exists('g:loaded_nohlsearch')
finish
endif
let g:loaded_nohlsearch = 1
augroup nohlsearch
au!
noremap <Plug>(nohlsearch) <cmd>nohlsearch<cr>
noremap! <Plug>(nohlsearch) <cmd>nohlsearch<cr>
au CursorHold * call feedkeys("\<Plug>(nohlsearch)", 'm')
au InsertEnter * call feedkeys("\<Plug>(nohlsearch)", 'm')
augroup END

View File

@@ -0,0 +1,104 @@
" When you're writing shell scripts and you are in doubt which test to use,
" which shell environment variables are defined, what the syntax of the case
" statement is, and you need to invoke 'man sh'?
"
" Your problems are over now!
"
" Attached is a Vim script file for turning gvim into a shell script editor.
" It may also be used as an example how to use menus in Vim.
"
" Maintainer: Ada (Haowen) Yu <me@yuhaowen.com>
" Original author: Lennart Schultz <les@dmi.min.dk> (mail unreachable)
" Make sure the '<' and 'C' flags are not included in 'cpoptions', otherwise
" <CR> would not be recognized. See ":help 'cpoptions'".
let s:cpo_save = &cpo
set cpo&vim
imenu ShellMenu.Statements.for for in <CR>do<CR><CR>done<esc>ki <esc>kk0elli
imenu ShellMenu.Statements.case case in<CR>) ;;<CR>esac<esc>bki <esc>k0elli
imenu ShellMenu.Statements.if if <CR>then<CR><CR>fi<esc>ki <esc>kk0elli
imenu ShellMenu.Statements.if-else if <CR>then<CR><CR>else<CR><CR>fi<esc>ki <esc>kki <esc>kk0elli
imenu ShellMenu.Statements.elif elif <CR>then<CR><CR><esc>ki <esc>kk0elli
imenu ShellMenu.Statements.while while do<CR><CR>done<esc>ki <esc>kk0elli
imenu ShellMenu.Statements.break break
imenu ShellMenu.Statements.continue continue
imenu ShellMenu.Statements.function () {<CR><CR>}<esc>ki <esc>k0i
imenu ShellMenu.Statements.return return
imenu ShellMenu.Statements.return-true return 0
imenu ShellMenu.Statements.return-false return 1
imenu ShellMenu.Statements.exit exit
imenu ShellMenu.Statements.shift shift
imenu ShellMenu.Statements.trap trap
imenu ShellMenu.Test.Existence [ -e ]<esc>hi
imenu ShellMenu.Test.Existence\ -\ file [ -f ]<esc>hi
imenu ShellMenu.Test.Existence\ -\ file\ (not\ empty) [ -s ]<esc>hi
imenu ShellMenu.Test.Existence\ -\ directory [ -d ]<esc>hi
imenu ShellMenu.Test.Existence\ -\ executable [ -x ]<esc>hi
imenu ShellMenu.Test.Existence\ -\ readable [ -r ]<esc>hi
imenu ShellMenu.Test.Existence\ -\ writable [ -w ]<esc>hi
imenu ShellMenu.Test.String\ is\ empty [ x = "x$" ]<esc>hhi
imenu ShellMenu.Test.String\ is\ not\ empty [ x != "x$" ]<esc>hhi
imenu ShellMenu.Test.Strings\ are\ equal [ "" = "" ]<esc>hhhhhhhi
imenu ShellMenu.Test.Strings\ are\ not\ equal [ "" != "" ]<esc>hhhhhhhhi
imenu ShellMenu.Test.Value\ is\ greater\ than [ -gt ]<esc>hhhhhhi
imenu ShellMenu.Test.Value\ is\ greater\ equal [ -ge ]<esc>hhhhhhi
imenu ShellMenu.Test.Values\ are\ equal [ -eq ]<esc>hhhhhhi
imenu ShellMenu.Test.Values\ are\ not\ equal [ -ne ]<esc>hhhhhhi
imenu ShellMenu.Test.Value\ is\ less\ than [ -lt ]<esc>hhhhhhi
imenu ShellMenu.Test.Value\ is\ less\ equal [ -le ]<esc>hhhhhhi
imenu ShellMenu.ParmSub.Substitute\ word\ if\ parm\ not\ set ${:-}<esc>hhi
imenu ShellMenu.ParmSub.Set\ parm\ to\ word\ if\ not\ set ${:=}<esc>hhi
imenu ShellMenu.ParmSub.Substitute\ word\ if\ parm\ set\ else\ nothing ${:+}<esc>hhi
imenu ShellMenu.ParmSub.If\ parm\ not\ set\ print\ word\ and\ exit ${:?}<esc>hhi
imenu ShellMenu.SpShVars.Number\ of\ positional\ parameters ${#}
imenu ShellMenu.SpShVars.All\ positional\ parameters\ (quoted\ spaces) ${*}
imenu ShellMenu.SpShVars.All\ positional\ parameters\ (unquoted\ spaces) ${@}
imenu ShellMenu.SpShVars.Flags\ set ${-}
imenu ShellMenu.SpShVars.Return\ code\ of\ last\ command ${?}
imenu ShellMenu.SpShVars.Process\ number\ of\ this\ shell ${$}
imenu ShellMenu.SpShVars.Process\ number\ of\ last\ background\ command ${!}
imenu ShellMenu.Environ.HOME ${HOME}
imenu ShellMenu.Environ.PATH ${PATH}
imenu ShellMenu.Environ.CDPATH ${CDPATH}
imenu ShellMenu.Environ.MAIL ${MAIL}
imenu ShellMenu.Environ.MAILCHECK ${MAILCHECK}
imenu ShellMenu.Environ.PS1 ${PS1}
imenu ShellMenu.Environ.PS2 ${PS2}
imenu ShellMenu.Environ.IFS ${IFS}
imenu ShellMenu.Environ.SHACCT ${SHACCT}
imenu ShellMenu.Environ.SHELL ${SHELL}
imenu ShellMenu.Environ.LC_CTYPE ${LC_CTYPE}
imenu ShellMenu.Environ.LC_MESSAGES ${LC_MESSAGES}
imenu ShellMenu.Builtins.cd cd
imenu ShellMenu.Builtins.echo echo
imenu ShellMenu.Builtins.eval eval
imenu ShellMenu.Builtins.exec exec
imenu ShellMenu.Builtins.export export
imenu ShellMenu.Builtins.getopts getopts
imenu ShellMenu.Builtins.hash hash
imenu ShellMenu.Builtins.newgrp newgrp
imenu ShellMenu.Builtins.pwd pwd
imenu ShellMenu.Builtins.read read
imenu ShellMenu.Builtins.readonly readonly
imenu ShellMenu.Builtins.return return
imenu ShellMenu.Builtins.times times
imenu ShellMenu.Builtins.type type
imenu ShellMenu.Builtins.umask umask
imenu ShellMenu.Builtins.wait wait
imenu ShellMenu.Set.set set
imenu ShellMenu.Set.unset unset
imenu ShellMenu.Set.Mark\ created\ or\ modified\ variables\ for\ export set -a
imenu ShellMenu.Set.Exit\ when\ command\ returns\ non-zero\ status set -e
imenu ShellMenu.Set.Disable\ file\ name\ expansion set -f
imenu ShellMenu.Set.Locate\ and\ remember\ commands\ when\ being\ looked\ up set -h
imenu ShellMenu.Set.All\ assignment\ statements\ are\ placed\ in\ the\ environment\ for\ a\ command set -k
imenu ShellMenu.Set.Read\ commands\ but\ do\ not\ execute\ them set -n
imenu ShellMenu.Set.Exit\ after\ reading\ and\ executing\ one\ command set -t
imenu ShellMenu.Set.Treat\ unset\ variables\ as\ an\ error\ when\ substituting set -u
imenu ShellMenu.Set.Print\ shell\ input\ lines\ as\ they\ are\ read set -v
imenu ShellMenu.Set.Print\ commands\ and\ their\ arguments\ as\ they\ are\ executed set -x
" Restore the previous value of 'cpoptions'.
let &cpo = s:cpo_save
unlet s:cpo_save

View File

@@ -0,0 +1,22 @@
" These macros swap the left and right mouse buttons (for left handed)
" Don't forget to do ":set mouse=a" or the mouse won't work at all
noremap <LeftMouse> <RightMouse>
noremap <2-LeftMouse> <2-RightMouse>
noremap <3-LeftMouse> <3-RightMouse>
noremap <4-LeftMouse> <4-RightMouse>
noremap <LeftDrag> <RightDrag>
noremap <LeftRelease> <RightRelease>
noremap <RightMouse> <LeftMouse>
noremap <2-RightMouse> <2-LeftMouse>
noremap <3-RightMouse> <3-LeftMouse>
noremap <4-RightMouse> <4-LeftMouse>
noremap <RightDrag> <LeftDrag>
noremap <RightRelease> <LeftRelease>
noremap g<LeftMouse> <C-RightMouse>
noremap g<RightMouse> <C-LeftMouse>
noremap! <LeftMouse> <RightMouse>
noremap! <LeftDrag> <RightDrag>
noremap! <LeftRelease> <RightRelease>
noremap! <RightMouse> <LeftMouse>
noremap! <RightDrag> <LeftDrag>
noremap! <RightRelease> <LeftRelease>

File diff suppressed because it is too large Load Diff