├── LICENSE ├── README.md └── autoload └── notifications.vim /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kristijan Husak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-simple-notifications 2 | Simple notification plugin to be used mostly by plugin developers. 3 | Uses floating/popup window if available, and falls back to `echo` if popups are not available. 4 | 5 | Can be used as a standalone plugin: 6 | ```vimL 7 | " Vim packager 8 | call packager#add('kristijanhusak/vim-simple-notifications') 9 | " Vim Plug 10 | Plug 'kristijanhusak/vim-simple-notifications' 11 | ``` 12 | 13 | or just copy `autoload/notifications.vim` file to your `autoload` folder, adapt public function names and you're good to go. 14 | 15 | ## Example usage 16 | All options provided via 2nd argument have default options. Check [autoload/notifications.vim](autoload/notifications.vim#L7) 17 | 18 | ### notifications#info(msg: [List|String], opts: Dictionary) 19 | ```vimL 20 | call notifications#info(['This is an info notification', 'that spans multiple lines']) 21 | ``` 22 | ![screenshot-info](https://i.imgur.com/7dstZFc.png) 23 | 24 | ### notifications#warning(msg: [List|String], opts: Dictionary) 25 | ```vimL 26 | call notifications#warning(['This is a warning', 'in the top left corner'], {'pos': 'topleft'}) 27 | ``` 28 | ![screenshot-warning](https://i.imgur.com/hN47hhx.png) 29 | 30 | ### notifications#error(msg: [List|String], opts: Dictionary) 31 | ```vimL 32 | call notifications#error(['Something went wrong', 'Please check your logs'], {'pos': 'top', 'width': 30, 'delay': 10000 }) 33 | ``` 34 | ![screenshot-error](https://i.imgur.com/3cQJiiB.png) 35 | 36 | With title 37 | ```vimL 38 | call notifications#error(['Something went wrong', 'Please check your logs'], {'title': '[MyAwesomePlugin]'}) 39 | ``` 40 | ![screenshot-error-title](https://i.imgur.com/BxegSKz.png) 41 | -------------------------------------------------------------------------------- /autoload/notifications.vim: -------------------------------------------------------------------------------- 1 | " Author: Kristijan Husak 2 | " Github: http://github.com/kristijanhusak 3 | " Original: http://github.com/kristijanhusak/vim-simple-notifications 4 | " LICENSE: MIT 5 | 6 | " Default options, overrideable via second argument to functions 7 | let s:delay = 7000 "Hide after this number of milliseconds 8 | let s:width = 40 "Default notification width 9 | let s:pos = 'topright' "Default position for notification 10 | let s:title = '' "Title on notification 11 | 12 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 13 | " Public API, adapt names to your needs " 14 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 15 | 16 | function notifications#info(msg, ...) abort 17 | return s:notification(a:msg, get(a:, 1, {})) 18 | endfunction 19 | 20 | function notifications#error(msg, ...) abort 21 | return s:notification(a:msg, extend({'type': 'error'}, get(a:, 1, {}))) 22 | endfunction 23 | 24 | function notifications#warning(msg, ...) abort 25 | return s:notification(a:msg, extend({'type': 'warning'}, get(a:, 1, {}))) 26 | endfunction 27 | 28 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 29 | " Implementation " 30 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 31 | let s:win = -1 32 | let s:timer = -1 33 | let s:neovim_float = has('nvim') && exists('*nvim_open_win') 34 | let s:vim_popup = exists('*popup_create') 35 | 36 | function! s:notification(msg, opts) abort 37 | if empty(a:msg) 38 | return 39 | endif 40 | let use_echo = get(a:opts, 'echo', 0) 41 | 42 | if s:neovim_float && !use_echo 43 | return s:notification_nvim(a:msg, a:opts) 44 | endif 45 | 46 | if s:vim_popup && !use_echo 47 | return s:notification_vim(a:msg, a:opts) 48 | endif 49 | 50 | return s:notification_echo(a:msg, a:opts) 51 | endfunction 52 | 53 | let s:hl_by_type = { 54 | \ 'info': 'NotificationInfo', 55 | \ 'error': 'NotificationError', 56 | \ 'warning': 'NotificationWarning', 57 | \ } 58 | 59 | function! s:nvim_close() abort 60 | silent! call nvim_win_close(s:win, v:true) 61 | silent! call timer_stop(s:timer) 62 | endfunction 63 | 64 | function! s:notification_nvim(msg, opts) abort 65 | let width = get(a:opts, 'width', s:width) 66 | let title = get(a:opts, 'title', s:title) 67 | let msg = type(a:msg) !=? type([]) ? [a:msg] : a:msg 68 | if !empty(title) 69 | let msg = [title] + msg 70 | endif 71 | 72 | let height = 0 73 | for line in msg 74 | let height += len(split(line,'.\{'.width.'}\zs')) 75 | endfor 76 | let delay = get(a:opts, 'delay', s:delay) 77 | let type = get(a:opts, 'type', 'info') 78 | let pos = get(a:opts, 'pos', s:pos) 79 | let pos_map = {'topleft': 'NW', 'topright': 'NE', 'botleft': 'SW', 'botright': 'SE', 'top': 'NW', 'bottom': 'SW'} 80 | 81 | let pos_opts = s:get_pos(pos, width) 82 | let pos_opts.anchor = pos_map[pos] 83 | let opts = extend(pos_opts, { 84 | \ 'relative': 'editor', 85 | \ 'width': width, 86 | \ 'height': height, 87 | \ 'style': 'minimal', 88 | \ }) 89 | 90 | call s:nvim_close() 91 | let buf = nvim_create_buf(v:false, v:true) 92 | call nvim_buf_set_lines(buf, 0, -1, v:false, msg) 93 | 94 | let s:win = nvim_open_win(buf, v:false, opts) 95 | silent! exe 'autocmd BufEnter :bw!' 96 | call nvim_win_set_option(s:win, 'wrap', v:true) 97 | call nvim_win_set_option(s:win, 'signcolumn', 'yes') "simulate left padding 98 | call nvim_win_set_option(s:win, 'winhl', 'Normal:'.s:hl_by_type[type]) 99 | let s:timer = timer_start(delay, {-> s:nvim_close()}) 100 | endfunction 101 | 102 | function! s:notification_vim(msg, opts) abort 103 | let width = get(a:opts, 'width', s:width) 104 | let delay = get(a:opts, 'delay', s:delay) 105 | let type = get(a:opts, 'type', 'info') 106 | let pos = get(a:opts, 'pos', s:pos) 107 | let title = get(a:opts, 'title', s:title) 108 | let pos_opts = s:get_pos(pos, width) 109 | let pos_opts.line = pos_opts.row 110 | unlet! pos_opts.row 111 | let pos_map = {'top': 'topleft', 'bottom': 'botleft'} 112 | let pos = has_key(pos_map, pos) ? pos_map[pos] : pos 113 | let opts = extend(pos_opts, { 114 | \ 'pos': pos, 115 | \ 'minwidth': width, 116 | \ 'maxwidth': width, 117 | \ 'time': delay, 118 | \ 'close': 'click', 119 | \ 'title': title, 120 | \ 'padding': [0, 0, 0, 1], 121 | \ }) 122 | 123 | let opts.highlight = s:hl_by_type[type] 124 | call popup_hide(s:win) 125 | let s:win = popup_create(a:msg, opts) 126 | endfunction 127 | 128 | function! s:notification_echo(msg, opts) abort 129 | let type = get(a:opts, 'type', 'info') 130 | let title = get(a:opts, 'title', s:title) 131 | silent! exe 'echohl Echo'.s:hl_by_type[type] 132 | redraw! 133 | let title = !empty(title) ? title.' ' : '' 134 | if type(a:msg) ==? type('') 135 | echom title.a:msg 136 | elseif type(a:msg) !=? type([]) 137 | echom title.string(a:msg) 138 | else 139 | echom title.a:msg[0] 140 | for msg in a:msg[1:] 141 | echom msg 142 | endfor 143 | endif 144 | echohl None 145 | endfunction 146 | 147 | function! s:setup_colors() abort 148 | let warning_fg = '' 149 | let warning_bg = '' 150 | let error_fg = '' 151 | let error_bg = '' 152 | let normal_fg = '' 153 | let normal_bg = '' 154 | let warning_bg = synIDattr(hlID('WarningMsg'), 'bg') 155 | let warning_fg = synIDattr(hlID('WarningMsg'), 'fg') 156 | if empty(warning_bg) 157 | let warning_bg = warning_fg 158 | let warning_fg = '#FFFFFF' 159 | endif 160 | 161 | let error_bg = synIDattr(hlID('Error'), 'bg') 162 | let error_fg = synIDattr(hlID('Error'), 'fg') 163 | if empty(error_bg) 164 | let error_bg = error_fg 165 | let error_fg = '#FFFFFF' 166 | endif 167 | 168 | let normal_bg = synIDattr(hlID('Normal'), 'bg') 169 | let normal_fg = synIDattr(hlID('Normal'), 'fg') 170 | if empty(normal_bg) 171 | let normal_bg = normal_fg 172 | let normal_fg = '#FFFFFF' 173 | endif 174 | 175 | call s:set_hl('NotificationInfo', normal_bg, normal_fg) 176 | call s:set_hl('NotificationError', error_fg, error_bg) 177 | call s:set_hl('NotificationWarning', warning_fg, warning_bg) 178 | 179 | call s:set_hl('EchoNotificationInfo', normal_fg, 'NONE') 180 | call s:set_hl('EchoNotificationError', error_bg, 'NONE') 181 | call s:set_hl('EchoNotificationWarning', warning_bg, 'NONE') 182 | endfunction 183 | 184 | function! s:get_pos(pos, width) abort 185 | let min_col = s:neovim_float ? 1 : 2 186 | let min_row = s:neovim_float ? 0 : 1 187 | let max_col = &columns - 1 188 | let max_row = &lines - 3 189 | let pos_data = {'col': min_col, 'row': min_row} 190 | 191 | if a:pos ==? 'top' 192 | let pos_data.row = min_row 193 | let pos_data.col = (&columns / 2) - (a:width / 2) 194 | endif 195 | 196 | if a:pos ==? 'bottom' 197 | let pos_data.row = max_row 198 | let pos_data.col = (&columns / 2) - (a:width / 2) 199 | endif 200 | 201 | if a:pos ==? 'topright' 202 | let pos_data.col = max_col 203 | let pos_data.row = min_row 204 | endif 205 | 206 | if a:pos ==? 'botleft' 207 | let pos_data.col = min_col 208 | let pos_data.row = max_row 209 | endif 210 | 211 | if a:pos ==? 'botright' 212 | let pos_data.col = max_col 213 | let pos_data.row = max_row 214 | endif 215 | 216 | return pos_data 217 | endfunction 218 | 219 | function! s:set_hl(name, fg, bg) abort 220 | if !hlexists(a:name) 221 | silent! exe 'hi '.a:name.' guifg='.a:fg.' guibg='.a:bg 222 | endif 223 | endfunction 224 | 225 | call s:setup_colors() 226 | --------------------------------------------------------------------------------