diff --git a/README.md b/README.md index d64dcc5..501f2df 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,18 @@ Readme! 1. nvim-cmp's breaking changes are documented [here](https://github.com/hrsh7th/nvim-cmp/issues/231). 2. This is my hobby project. You can support me via GitHub sponsors. 3. Bug reports are welcome, but I might not fix if you don't provide a minimal reproduction configuration and steps. +4. The nvim-cmp documents is [here](./doc/cmp.txt). + Concept ==================== +- Full support for LSP completion related capabilities +- Powerful customizability via Lua functions +- Smart handling of key mapping - No flicker -- Works properly -- Fully customizable via Lua functions -- Fully supports LSP's completion capabilities - - Snippets - - CommitCharacters - - TriggerCharacters - - TextEdit and InsertReplaceTextEdit - - AdditionalTextEdits - - Markdown documentation - - Execute commands (Some LSP server needs it to auto-importing. e.g. `sumneko_lua` or `purescript-language-server`) - - Preselect - - CompletionItemTags -- Support pairs-wise plugin automatically + Setup @@ -78,8 +71,8 @@ lua <'] = cmp.mapping.confirm({ select = true }), + [''] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. }, sources = cmp.config.sources({ { name = 'nvim_lsp' }, @@ -130,7 +121,6 @@ lua <` mappings and integration with snippets. ### Where can I find more completion sources? @@ -140,662 +130,77 @@ You can search for various completion sources [here](https://github.com/topics/n Please see the corresponding [FAQ](#how-to-show-name-of-item-kind-and-source-like-compe) section or [Wiki pages](https://github.com/hrsh7th/nvim-cmp/wiki). -Configuration options + +Advanced configuration example ==================== -You can specify the following configuration options via `cmp.setup { ... }`. +### Use nvim-cmp as smart omnifunc handler. -The configuration options will be merged with the [default config](./lua/cmp/config/default.lua). - -If you want to remove a default option, set it to `false`. - - -#### mapping (type: table) - -Defines the action of each key mapping. The following lists all the built-in actions: - -- `cmp.mapping.select_prev_item({ cmp.SelectBehavior.{Insert,Select} })` -- `cmp.mapping.select_next_item({ cmp.SelectBehavior.{Insert,Select} })` -- `cmp.mapping.scroll_docs(number)` -- `cmp.mapping.complete({ reason = cmp.ContextReason.{Manual,Auto} })` -- `cmp.mapping.close()` -- `cmp.mapping.abort()` -- `cmp.mapping.confirm({ select = bool, behavior = cmp.ConfirmBehavior.{Insert,Replace} })`: If `select` is true and you haven't select any item, automatically selects the first item. - -You can configure `nvim-cmp` to use these `cmp.mapping` like this: +nvim-cmp can be used as flexible omnifunc manager. ```lua -mapping = { - [''] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }), - [''] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }), - [''] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }), - [''] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }), - [''] = cmp.mapping.scroll_docs(-4), - [''] = cmp.mapping.scroll_docs(4), - [''] = cmp.mapping.complete(), - [''] = cmp.mapping.close(), - [''] = cmp.mapping.confirm({ - behavior = cmp.ConfirmBehavior.Replace, - select = true, - }) -} -``` - -In addition, the mapping mode can be specified with the help of `cmp.mapping(...)`. The default is the insert mode (i) if not specified. - -```lua -mapping = { - ... - [''] = cmp.mapping(cmp.mapping.select_next_item(), { 'i', 's' }) - ... -} -``` - -The mapping mode can also be specified using a table. This is particularly useful to set different actions for each mode. - -```lua -mapping = { - [''] = cmp.mapping({ - i = cmp.mapping.confirm({ select = true }), - c = cmp.mapping.confirm({ select = false }), - }) -} -``` - -You can also provide a custom function as the action. - -```lua -mapping = { - [''] = function(fallback) - if ...some_condition... then - vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('...', true, true, true), 'n', true) - else - fallback() -- The fallback function is treated as original mapped key. In this case, it might be ``. - end - end, -} -``` - -#### enabled (type: fun(): boolean|boolean) - -A boolean value, or a function returning a boolean, that specifies whether to enable nvim-cmp's features or not. - -Default: - -```lua -function() - return vim.api.nvim_buf_get_option(0, 'buftype') ~= 'prompt' -end -``` - -#### sources (type: table) - -Lists all the global completion sources that will be enabled in all buffers. -The order of the list defines the priority of each source. See the -*sorting.priority_weight* option below. - -It is possible to set up different sources for different filetypes using -`FileType` autocommand and `cmp.setup.buffer` to override the global -configuration. - -```viml -" Setup buffer configuration (nvim-lua source only enables in Lua filetype). -autocmd FileType lua lua require'cmp'.setup.buffer { -\ sources = { -\ { name = 'nvim_lua' }, -\ { name = 'buffer' }, -\ }, -\ } -``` - -Note that the source name isn't necessarily the source repository name. Source -names are defined in the source repository README files. For example, look at -the [hrsh7th/cmp-buffer](https://github.com/hrsh7th/cmp-buffer) source README -which defines the source name as `buffer`. - -#### sources[number].name (type: string) - -The source name. - -#### sources[number].option (type: table) - -The source customization options. It is defined by each source. - -#### sources[number].priority (type: number|nil) - -The priority of the source. If you don't specify it, the source priority will -be determined by the default algorithm (see `sorting.priority_weight`). - -#### sources[number].trigger_characters (type: string[]) - -The source specific triggerCharacters for override. - -#### sources[number].keyword_pattern (type: string) - -The source specific keyword_pattern for override. - -#### sources[number].keyword_length (type: number) - -The source specific keyword_length for override. - -#### sources[number].max_item_count (type: number) - -The source specific maximum item count. - -#### sources[number].group_index (type: number) - -The source group index. - -You can call built-in utility like `cmp.config.sources({ { name = 'a' } }, { { name = 'b' } })`. - -#### preselect (type: cmp.PreselectMode) - -Specify preselect mode. The following modes are available. - -- `cmp.PreselectMode.Item` - - If the item has `preselect = true`, `nvim-cmp` will preselect it. -- `cmp.PreselectMode.None` - - Disable preselect feature. - -Default: `cmp.PreselectMode.Item` - -#### completion.autocomplete (type: cmp.TriggerEvent[]) - -Which events should trigger `autocompletion`. - -If you set this to `false`, `nvim-cmp` will not perform completion -automatically. You can still use manual completion though (like omni-completion -via the `cmp.mapping.complete` function). - -Default: `{ types.cmp.TriggerEvent.TextChanged }` - -#### completion.keyword_pattern (type: string) - -The default keyword pattern. This value will be used if a source does not set -a source specific pattern. - -Default: `[[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]]` - -#### completion.keyword_length (type: number) - -The minimum length of a word to complete on; e.g., do not try to complete when the -length of the word to the left of the cursor is less than `keyword_length`. - -Default: `1` - -#### completion.get_trigger_characters (type: fun(trigger_characters: string[]): string[]) - -The function to resolve trigger_characters. - -Default: `function(trigger_characters) return trigger_characters end` - -#### completion.completeopt (type: string) - -vim's `completeopt` setting. Warning: Be careful when changing this value. - -Default: `menu,menuone,noselect` - -#### confirmation.default_behavior (type: cmp.ConfirmBehavior) - -A default `cmp.ConfirmBehavior` value when to use confirmed by commitCharacters - -Default: `cmp.ConfirmBehavior.Insert` - -#### confirmation.get_commit_characters (type: fun(commit_characters: string[]): string[]) - -The function to resolve commit_characters. - -#### sorting.priority_weight (type: number) - -The score multiplier of source when calculating the items' priorities. -Specifically, each item's original priority (given by its corresponding source) -will be increased by `#sources - (source_index - 1)` multiplied by -`priority_weight`. That is, the final priority is calculated by the following formula: - -`final_score = orig_score + ((#sources - (source_index - 1)) * sorting.priority_weight)` - -Default: `2` - -#### sorting.comparators (type: function[]) - -When sorting completion items, the sort logic tries each function in -`sorting.comparators` consecutively when comparing two items. The first function -to return something other than `nil` takes precedence. - -Each function must return `boolean|nil`. - -You can use the preset functions from `cmp.config.compare.*`. - -Default: -```lua -{ - cmp.config.compare.offset, - cmp.config.compare.exact, - cmp.config.compare.score, - cmp.config.compare.recently_used, - cmp.config.compare.kind, - cmp.config.compare.sort_text, - cmp.config.compare.length, - cmp.config.compare.order, -} -``` - -#### documentation (type: false | cmp.DocumentationConfig) - -If set to `false`, the documentation of each item will not be shown. -Else, a table representing documentation configuration should be provided. -The following are the possible options: - -#### documentation.border (type: string[]) - -Border characters used for documentation window. - -#### documentation.winhighlight (type: string) - -A neovim's `winhighlight` option for documentation window. - -#### documentation.maxwidth (type: number) - -The documentation window's max width. - -#### documentation.maxheight (type: number) - -The documentation window's max height. - -#### documentation.zindex (type: number) - -The documentation window's zindex. - -#### formatting.fields (type: cmp.ItemField[]) - -The order of item's fields for completion menu. - -#### formatting.format (type: fun(entry: cmp.Entry, vim_item: vim.CompletedItem): vim.CompletedItem) - -A function to customize completion menu. -The return value is defined by vim. See `:help complete-items`. - -You can display the fancy icons to completion-menu with [lspkind-nvim](https://github.com/onsails/lspkind-nvim). - -Please see [FAQ](#how-to-show-name-of-item-kind-and-source-like-compe) if you would like to show symbol-text (e.g. function) and source (e.g. LSP) like compe. - -```lua -local lspkind = require('lspkind') +local cmp = require('cmp') cmp.setup { - formatting = { - format = lspkind.cmp_format(), + completion = { + autocomplete = false, -- disable auto-completion. }, } -``` -See the [wiki](https://github.com/hrsh7th/nvim-cmp/wiki/Menu-Appearance#basic-customisations) for more info on customizing menu appearance. - -#### experimental.native_menu (type: boolean) - -Use vim's native completion menu instead of custom floating menu. - -Default: `false` - -#### experimental.ghost_text (type: cmp.GhostTextConfig | false) - -Specify whether to display ghost text. - -Default: `false` - -Commands -==================== - -#### `CmpStatus` - -Show the source statuses - -Autocmds -==================== - -#### `cmp#ready` - -Invoke after nvim-cmp setup. - -Highlights -==================== - -#### `CmpItemAbbr` - -The abbr field. - -#### `CmpItemAbbrDeprecated` - -The deprecated item's abbr field. - -#### `CmpItemAbbrMatch` - -The matched characters highlight. - -#### `CmpItemAbbrMatchFuzzy` - -The fuzzy matched characters highlight. - -#### `CmpItemKind` - -The kind field. - -#### `CmpItemKind%KIND_NAME%` - -The specific kind highlights. -You can see the name on [lsp.lua#L108](./lua/cmp/types/lsp.lua#L108). - -For example, You can change the highlight like this if you want to override only the `Method` kind. - -``` -highlight! CmpItemKindMethod guibg=NONE guifg=LightYellow -``` - - -#### `CmpItemMenu` - -The menu field. - -Programatic API -==================== - -You can use the following APIs. - -#### `cmp.event:on(name: string, callback: string)` - -Subscribes to the following events. - -- `confirm_done` - -#### `cmp.get_config()` - -Returns the current configuration. - -#### `cmp.visible()` - -Returns the completion menu is visible or not. - -NOTE: This method returns true if the native popup menu is visible, for the convenience of defining mappings. - -#### `cmp.get_selected_entry()` - -Returns the selected entry. - -#### `cmp.get_active_entry()` - -Returns the active entry. - -NOTE: The `preselected` entry does not returned from this method. - -#### `cmp.confirm({ select = boolean, behavior = cmp.ConfirmBehavior.{Insert,Replace} }, callback)` - -Confirms the current selected item, if possible. If `select` is true and no item has been selected, selects the first item. - -#### `cmp.complete({ reason = cmp.ContextReason.{Manual,Auto} })` - -Invokes manual completion. - -NOTE: manual completion overrules some checks autocompletion does like `keyword_length`. -To make it behave like autocompletion instead, you can overwrite the reason in the argument. - -#### `cmp.close()` - -Closes the current completion menu. - -#### `cmp.abort()` - -Closes the current completion menu and restore the current line (similar to native `` behavior). - -#### `cmp.select_next_item({ cmp.SelectBehavior.{Insert,Select} })` - -Selects the next completion item if possible. - -#### `cmp.select_prev_item({ cmp.SelectBehavior.{Insert,Select} })` - -Selects the previous completion item if possible. - -#### `cmp.scroll_docs(delta)` - -Scrolls the documentation window by `delta` lines, if possible. - - -FAQ -==================== - -#### I can't get the specific source working. - -Check the output of command `:CmpStatus`. It is likely that you specify the source name incorrectly. - -NOTE: `nvim_lsp` will be sourced on `InsertEnter` event. It will show as `unknown source`, but this isn't a problem. - - -#### What is the `pair-wise plugin automatically supported`? - -Some pair-wise plugin set up the mapping automatically. -For example, `vim-endwise` will map `` even if you don't do any mapping instructions for the plugin. - -But I think the user want to override `` mapping only when the mapping item is selected. - -The `nvim-cmp` does it automatically. - -The following configuration will be working as - -1. If the completion-item is selected, will be working as `cmp.mapping.confirm`. -2. If the completion-item isn't selected, will be working as vim-endwise feature. - -```lua -mapping = { - [''] = cmp.mapping.confirm() -} -``` - - -#### What is the equivalence of nvim-compe's `preselect = 'always'`? - -You can use the following configuration. - -```lua -cmp.setup { - completion = { - completeopt = 'menu,menuone,noinsert', - } -} -``` - -#### I don't use a snippet plugin. - -At the moment, nvim-cmp requires a snippet engine to function correctly. -You need to specify one in `snippet`. - -```lua -snippet = { - -- REQUIRED - you must specify a snippet engine - expand = function(args) - vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users. - -- require('luasnip').lsp_expand(args.body) -- For `luasnip` users. - -- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users. - -- require'snippy'.expand_snippet(args.body) -- For `snippy` users. - end, -} -``` - - -#### I dislike auto-completion - -You can use `nvim-cmp` without auto-completion like this. - -```lua -cmp.setup { - completion = { - autocomplete = false - } -} -``` - - -#### How to disable nvim-cmp on the specific buffer? - -You can specify `enabled = false` like this. - -```vim -autocmd FileType TelescopePrompt lua require('cmp').setup.buffer { enabled = false } -``` - - -#### nvim-cmp is slow. - -I've optimized `nvim-cmp` as much as possible, but there are currently some known / unfixable issues. - -**`cmp-buffer` source and too large buffer** - -The `cmp-buffer` source makes an index of the current buffer so if the current buffer is too large, it will slowdown the main UI thread. - -**`vim.lsp.set_log_level`** - -This setting will cause the filesystem operation for each LSP payload. -This will greatly slow down nvim-cmp (and other LSP related features). - - -#### How to show name of item kind and source (like compe)? - -```lua -formatting = { - format = require("lspkind").cmp_format({with_text = true, menu = ({ - buffer = "[Buffer]", - nvim_lsp = "[LSP]", - luasnip = "[LuaSnip]", - nvim_lua = "[Lua]", - latex_symbols = "[Latex]", - })}), -}, -``` -See [Menu appearance](https://github.com/hrsh7th/nvim-cmp/wiki/Menu-Appearance) section from the [wiki](https://github.com/hrsh7th/nvim-cmp/wiki) for more information. - -#### How to set up mappings? - -You can find all the mapping examples in [Example mappings](https://github.com/hrsh7th/nvim-cmp/wiki/Example-mappings). - - -Create a Custom Source -==================== - -Warning: If the LSP spec is changed, nvim-cmp will keep up to it without an announcement. - -If you publish `nvim-cmp` source to GitHub, please add `nvim-cmp` topic for the repo. - -You should read [cmp types](/lua/cmp/types) and [LSP spec](https://microsoft.github.io/language-server-protocol/specifications/specification-current/) to create sources. - -- The `complete` function is required. Others can be omitted. -- The `callback` argument must always be called. -- The custom source should only use `require('cmp')`. -- The custom source can specify `word` property to CompletionItem. (It isn't an LSP specification but supported as a special case.) - -Here is an example of a custom source. - -```lua -local source = {} - ----Source constructor. -source.new = function() - local self = setmetatable({}, { __index = source }) - self.your_awesome_variable = 1 - return self +_G.vimrc = _G.vimrc or {} +_G.vimrc.cmp = _G.vimrc.cmp or {} +_G.vimrc.cmp.lsp = function() + cmp.complete({ + config = { + sources = { + { name = 'nvim_lsp' } + } + } + }) end - ----Return the source is available or not. ----@return boolean -function source:is_available() - return true -end - ----Return the source name for some information. -function source:get_debug_name() - return 'example' -end - ----Return keyword pattern which will be used... ---- 1. Trigger keyword completion ---- 2. Detect menu start offset ---- 3. Reset completion state ----@param params cmp.SourceBaseApiParams ----@return string -function source:get_keyword_pattern(params) - return '???' -end - ----Return trigger characters. ----@param params cmp.SourceBaseApiParams ----@return string[] -function source:get_trigger_characters(params) - return { ??? } -end - ----Invoke completion (required). ---- If you want to abort completion, just call the callback without arguments. ----@param params cmp.SourceCompletionApiParams ----@param callback fun(response: lsp.CompletionResponse|nil) -function source:complete(params, callback) - callback({ - { label = 'January' }, - { label = 'February' }, - { label = 'March' }, - { label = 'April' }, - { label = 'May' }, - { label = 'June' }, - { label = 'July' }, - { label = 'August' }, - { label = 'September' }, - { label = 'October' }, - { label = 'November' }, - { label = 'December' }, +_G.vimrc.cmp.snippet = function() + cmp.complete({ + config = { + sources = { + { name = 'vsnip' } + } + } }) end ----Resolve completion item that will be called when the item selected or before the item confirmation. ----@param completion_item lsp.CompletionItem ----@param callback fun(completion_item: lsp.CompletionItem|nil) -function source:resolve(completion_item, callback) - callback(completion_item) -end - ----Execute command that will be called when after the item confirmation. ----@param completion_item lsp.CompletionItem ----@param callback fun(completion_item: lsp.CompletionItem|nil) -function source:execute(completion_item, callback) - callback(completion_item) -end - -require('cmp').register_source(source.new()) +vim.cmd([[ + inoremap lua vimrc.cmp.lsp() + inoremap lua vimrc.cmp.snippet() +]]) ``` -You can also create a source by Vim script like this (This is useful to support callback style plugins). +### Full managed completion behavior. -- If you want to return `boolean`, you must return `v:true`/`v:false` instead of `0`/`1`. +```lua +local cmp = require('cmp') -```vim -let s:source = {} +cmp.setup { + completion = { + autocomplete = false, -- disable auto-completion. + } +} -function! s:source.new() abort - return extend(deepcopy(s:source)) -endfunction - -" The other APIs are also available. - -function! s:source.complete(params, callback) abort - call a:callback({ - \ { 'label': 'January' }, - \ { 'label': 'February' }, - \ { 'label': 'March' }, - \ { 'label': 'April' }, - \ { 'label': 'May' }, - \ { 'label': 'June' }, - \ { 'label': 'July' }, - \ { 'label': 'August' }, - \ { 'label': 'September' }, - \ { 'label': 'October' }, - \ { 'label': 'November' }, - \ { 'label': 'December' }, - \ }) -endfunction - -call cmp#register_source('month', s:source.new()) +_G.vimrc = _G.vimrc or {} +_G.vimrc.cmp = _G.vimrc.cmp or {} +_G.vimrc.cmp.on_text_changed = function() + local cursor = vim.api.nvim_win_get_cursor(0) + local line = vim.api.nvim_get_current_line() + local before = string.sub(line, 1, cursor[2] + 1) + if before:match('%s*$') then + cmp.complete() -- Trigger completion only if the cursor is placed at the end of line. + end +end +vim.cmd([[ + augroup vimrc + autocmd + autocmd TextChanged,TextChangedI,TextChangedP * call luaeval('vimrc.cmp.on_text_changed()') + augroup END +]]) ``` + + + diff --git a/doc/cmp.txt b/doc/cmp.txt index 47a5bdf..166287e 100644 --- a/doc/cmp.txt +++ b/doc/cmp.txt @@ -14,6 +14,7 @@ Command |cmp-command| Highlight |cmp-highlight| Autocmd |cmp-autocmd| Config |cmp-config| +Develop |cmp-develop| FAQ |cmp-faq| @@ -25,6 +26,8 @@ This is nvim-cmp's document. 1. This docs uses the type definition notation like `{lsp,cmp,vim}.*` - You can find it `../lua/cmp/types/init.lua`. +2. The advanced configuration is noted in wiki. + - https://github.com/hrsh7th/nvim-cmp/wiki @@ -34,6 +37,7 @@ Concept *cmp-concept* - Full support for LSP completion related capabilities - Powerful customizability via Lua functions - Smart handling of key mapping +- No flicker @@ -475,9 +479,127 @@ experimental.native_menu~ +============================================================================== +Develop *cmp-develop* + +Create custom source~ + +NOTE: + 1. The `complete` method is required. Others can be ommited. + 2. The `callback` argument must always be called. + 3. You can use only `require('cmp')` in custom source. + 4. If the LSP spec was changed, nvim-cmp will follow it without any announcement. + 5. You should read ./lua/cmp/types and https://microsoft.github.io/language-server-protocol/specifications/specification-current + 6. Please add the `nvim-cmp` topic for github repo. + +You can create custom source like the following example. + +> + local source = {} + + ---Return this source is available in current context or not. (Optional) + ---@return boolean + function source:is_available() + return true + end + + ---Return the debug name of this source. (Optional) + ---@return string + function source:get_debug_name() + return 'debug name' + end + + ---Return keyword pattern for triggering completion. (Optional) + ---If this is ommited, nvim-cmp will use default keyword pattern. See |cmp-config.completion.keyword_pattern| + ---@return string + function source:get_keyword_pattern() + return [[\k\+]] + end + + ---Return trigger characters for triggering completion. (Optional) + function source:get_trigger_characters() + return { '.' } + end + + ---Invoke completion. (Required) + ---@param params cmp.SourceCompletionApiParams + ---@param callback fun(response: lsp.CompletionResponse|nil) + function source:complete(params, callback) + callback({ + { label = 'January' }, + { label = 'February' }, + { label = 'March' }, + { label = 'April' }, + { label = 'May' }, + { label = 'June' }, + { label = 'July' }, + { label = 'August' }, + { label = 'September' }, + { label = 'October' }, + { label = 'November' }, + { label = 'December' }, + }) + end + + ---Resolve completion item. (Optional) + ---@param completion_item lsp.CompletionItem + ---@param callback fun(completion_item: lsp.CompletionItem|nil) + function source:resolve(completion_item, callback) + callback(completion_item) + end + + ---Execute command after item was accepted. + ---@param completion_item lsp.CompletionItem + ---@param callback fun(completion_item: lsp.CompletionItem|nil) + function source:execute(completion_item, callback) + callback(completion_item) + end + + ---Register custom source to nvim-cmp. + require('cmp').register_source('month', source.new()) +< + + ============================================================================== FAQ *cmp-faq* +How to disable auto-completion?~ +How to use nvim-cmp as like omnifunc?~ + +You can disable auto-completion like this. +> + cmp.setup { + ... + completion = { + autocomplete = false + } + ... + } +< +And you can invoke completion manually. +> + inoremap lua require('cmp').complete() +< + +How to disable nvim-cmp on the specific buffer?~ +How to setup on the specific buffer?~ + +You can setup buffer specific configuration like this. +> + autocmd FileType markdown * lua require('cmp').setup.buffer { + \ sources = { + \ { name = 'path' }, + \ { name = 'buffer' }, + \ } + \ } +< + +How to customize menu appearance?~ + +You can see the nvim-cmp wiki (https://github.com/hrsh7th/nvim-cmp/wiki). + + + ============================================================================== vim:tw=78:ts=4:et:ft=help:norl: