├── LICENSE ├── README.md ├── autoload └── notification.vim ├── doc ├── notification.txt └── screenshot.gif └── plugin └── notification.vim /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Yasuhiro Matsumoto 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-notification 2 | 3 | Message notification system for Vim 4 | 5 | ![](https://raw.githubusercontent.com/mattn/vim-notification/main/doc/screenshot.gif) 6 | 7 | ## Usage 8 | 9 | ```vim 10 | call notification#show('Hello World') 11 | ``` 12 | 13 | ```vim 14 | call notification#show(#{ 15 | \ text: 'Hello World', 16 | \}) 17 | ``` 18 | 19 | If you want to specify waiting time to stay the notification on screen: 20 | 21 | ```vim 22 | call notification#show(#{ 23 | \ text: 'Hello World', 24 | \ wait: 300, 25 | \}) 26 | ``` 27 | 28 | To handle clicked/closed event: 29 | 30 | ```vim 31 | function! s:my_clicked(data) abort 32 | echo a:data 33 | endfunction 34 | 35 | call notification#show(#{ 36 | \ text: 'Hello World', 37 | \ clicked: function('s:my_clicked', ['Hi!']), 38 | \}) 39 | ``` 40 | 41 | ## Installation 42 | 43 | For [vim-plug](https://github.com/junegunn/vim-plug) plugin manager: 44 | 45 | ```vim 46 | Plug 'mattn/vim-notification' 47 | ``` 48 | 49 | ## License 50 | 51 | MIT 52 | 53 | ## Author 54 | 55 | Yasuhiro Matsumoto (a.k.a. mattn) 56 | -------------------------------------------------------------------------------- /autoload/notification.vim: -------------------------------------------------------------------------------- 1 | let s:notifications = get(s:, 'notifications', []) 2 | let s:interval = get(g:, 'notification_interval', 50) 3 | 4 | function! s:strwidthpart(str, width) abort 5 | let l:str = tr(a:str, "\t", ' ') 6 | let l:vcol = a:width + 2 7 | return matchstr(l:str, '.*\%<' . (l:vcol < 0 ? 0 : l:vcol) . 'v') 8 | endfunction 9 | 10 | function! s:truncate(str, width) abort 11 | if a:str =~# '^[\x00-\x7f]*$' 12 | return len(a:str) < a:width 13 | \ ? printf('%-' . a:width . 's', a:str) 14 | \ : strpart(a:str, 0, a:width) 15 | endif 16 | 17 | let l:ret = a:str 18 | let l:width = strwidth(a:str) 19 | if l:width > a:width 20 | let l:ret = s:strwidthpart(l:ret, a:width) 21 | let l:width = strwidth(l:ret) 22 | endif 23 | 24 | if l:width < a:width 25 | let l:ret .= repeat(' ', a:width - l:width) 26 | endif 27 | 28 | return l:ret 29 | endfunction 30 | 31 | function! notification#terminate() abort 32 | for l:n in s:notifications 33 | let l:winid = l:n[0] 34 | call popup_close(l:winid) 35 | call s:call_by_winid(l:winid, 'closed') 36 | endfor 37 | endfunction 38 | 39 | function! s:callback(timer) abort 40 | let l:drop = [] 41 | let l:active = 0 42 | for l:n in s:notifications 43 | let [l:winid, l:options, l:context] = [l:n[0], l:n[1], l:n[2]] 44 | 45 | " calculate max width of the notification 46 | let l:width = max(map(copy(l:context.lines), 'min([strdisplaywidth(v:val), &columns - 4])')) 47 | 48 | " skip notifications which is still not located yet 49 | if !l:context.active 50 | continue 51 | endif 52 | let l:active += 1 53 | 54 | if l:context.count == 0 && &columns - l:options.col < l:width + 2 55 | " move left 56 | let l:options.col -= 1 57 | elseif l:context.count < l:context.wait 58 | " wait a while 59 | let l:context.count += s:interval 60 | elseif l:options.col < &columns - 1 61 | " move right 62 | let l:options.col += 1 63 | else 64 | " drop 65 | call popup_close(l:winid) 66 | call add(l:drop, l:winid) 67 | call s:call_by_winid(l:winid, 'closed') 68 | continue 69 | endif 70 | 71 | let l:lines = map(copy(l:context.lines), {i, v -> s:truncate(v, min([&columns - l:options.col, l:width]))}) 72 | call popup_settext(l:winid, l:lines) 73 | call popup_show(l:winid) 74 | call popup_setoptions(l:winid, l:options) 75 | endfor 76 | 77 | " remove notifications wiped out 78 | for l:v in l:drop 79 | let s:notifications = filter(s:notifications, 'l:v != v:val[0]') 80 | endfor 81 | 82 | " standby next notifications 83 | if l:active == 0 84 | let l:line = 2 85 | for l:n in s:notifications 86 | let l:n[1].line = l:line 87 | let l:line += 3 + len(l:n[2].lines) 88 | if l:line >= &lines 89 | break 90 | endif 91 | let l:n[2].active = v:true 92 | endfor 93 | endif 94 | 95 | " start timer if still have to do 96 | if len(s:notifications) > 0 97 | call timer_start(s:interval, function('s:callback')) 98 | endif 99 | endfunction 100 | 101 | function! notification#show(arg) abort 102 | let l:option = type(a:arg) == type({}) ? a:arg : {'text': a:arg} 103 | let l:title = get(l:option, 'title', '') 104 | " Hack: There's no way to detect mouse clicks on certain popup window other 105 | " than to enable 'close popup on mouse click' behavior. Make popup window 106 | " be closed on mouse click and handle it in popup callback when we have user 107 | " callback and need to handle mouse clicks on popup windows. 108 | let l:winid = popup_create( 109 | \ '', { 110 | \ 'padding': [1,1,1,1], 111 | \ 'hidden': v:true, 112 | \ 'mapping': v:true, 113 | \ 'title': l:title, 114 | \ 'close': has_key(l:option, 'clicked') ? 'click' : 'none', 115 | \ 'callback': function('s:on_popup_close'), 116 | \ }) 117 | let l:lines = split(get(l:option, 'text', ''), '\n') 118 | if len(l:lines) > &lines - &cmdheight - 5 119 | let l:lines = l:lines[:&lines - &cmdheight - 5] 120 | endif 121 | 122 | " calculate position 123 | let l:line = 2 124 | if !empty(s:notifications) 125 | let l:line = s:notifications[-1][1].line + len(s:notifications[-1][2].lines) + 3 126 | endif 127 | 128 | let l:opt = {'line': l:line, 'col': &columns} 129 | let l:ctx = {'lines': l:lines, 'title': l:title, 'count': 0, 'wait': get(l:option, 'wait', 5000), 'active': (l:line + 3 + len(l:lines)) < &lines} 130 | if has_key(l:option, 'clicked') 131 | let l:ctx.clicked = l:option.clicked 132 | endif 133 | if has_key(l:option, 'closed') 134 | let l:ctx.closed = l:option.closed 135 | endif 136 | let l:n = [l:winid, l:opt, l:ctx] 137 | call add(s:notifications, l:n) 138 | 139 | " start timer 140 | if len(s:notifications) == 1 141 | call timer_start(0, function('s:callback')) 142 | endif 143 | endfunction 144 | 145 | function! s:on_popup_close(winid, result) abort 146 | if a:result != -2 147 | return 148 | endif 149 | 150 | " Popup is closed by mouse click. Reopen popup and invoke 'clicked' user 151 | " handler. The reopened popup will closed with animation soon. 152 | for l:n in s:notifications 153 | if l:n[0] ==# a:winid 154 | let l:new_winid = popup_create(l:n[2].lines, { 155 | \ 'padding': [1,1,1,1], 156 | \ 'title': l:n[2].title, 157 | \ 'line': l:n[1].line, 158 | \ 'col': l:n[1].col, 159 | \ }) 160 | let l:n[0] = l:new_winid 161 | call s:call_by_winid(l:new_winid, 'clicked') 162 | return 163 | endif 164 | endfor 165 | endfunction 166 | 167 | function! s:call_by_winid(winid, fun) abort 168 | for l:n in s:notifications 169 | if l:n[0] ==# a:winid 170 | let l:n[2].count = l:n[2].wait 171 | if has_key(l:n[2], a:fun) 172 | let l:F = l:n[2][a:fun] 173 | try 174 | call l:F() 175 | catch 176 | endtry 177 | endif 178 | endif 179 | endfor 180 | endfunction 181 | 182 | function! s:closed(winid) abort 183 | call s:call_by_winid(a:winid, 'closed') 184 | endfunction 185 | -------------------------------------------------------------------------------- /doc/notification.txt: -------------------------------------------------------------------------------- 1 | *notification.txt* 2 | 3 | ------------------------------------------------------- 4 | The notification system for Vim 5 | ------------------------------------------------------- 6 | 7 | Author: Yasuhiro Matsumoto 8 | Repository: https://github.com/mattn/vim-notification 9 | License: MIT 10 | 11 | ============================================================================== 12 | CONTENTS *notification-contents* 13 | 14 | Introduction |notification-introduction| 15 | Install |notification-install| 16 | APIs |notification-api| 17 | 18 | ============================================================================== 19 | INTRODUCTION *notification-introduction* *notification* 20 | 21 | Notification is a utility plugin for displaying message notification. This 22 | plugin provides APIs to put notification popup window. 23 | 24 | ============================================================================== 25 | INSTALL *notification-install* 26 | 27 | Install the distributed files into Vim runtime directory which is usually 28 | '~/.vim/', or '$HOME/vimfiles' on Windows. 29 | 30 | If you install vim-plug (https://github.com/junegunn/vim-plug), add this line 31 | to your .vimrc file: 32 | > 33 | Plug 'mattn/vim-notification' 34 | < 35 | ============================================================================== 36 | APIs *notification-api* 37 | 38 | *notification#show* 39 | notification#show({expr}) 40 | If {expr} is a string, popup window is simply shown. If {expr} 41 | is a dictionary, possible entities are below: 42 | text text should be shown. 43 | title notification title. 44 | wait milli-sec time to stay on screen. 45 | clicked event handler for click. 46 | closed event handler for closed. 47 | 48 | Standard usage of this function: 49 | > 50 | call notification#show('Hello World') 51 | < 52 | Or below: 53 | > 54 | call notification#show(#{ 55 | \ text: 'Hello World', 56 | \}) 57 | < 58 | If you want to specify waiting time to stay the notification 59 | on screen: 60 | > 61 | call notification#show(#{ 62 | \ text: 'Hello World', 63 | \ wait: 300, 64 | \}) 65 | < 66 | To handle clicked/closed event: 67 | > 68 | function! s:my_clicked(data) abort 69 | echo a:data 70 | endfunction 71 | 72 | call notification#show(#{ 73 | \ text: 'Hello World', 74 | \ clicked: function('s:my_clicked', ['Hi!']), 75 | \}) 76 | < 77 | *notification#terminate* 78 | notification#terminate() 79 | Close all notification window. 80 | 81 | ============================================================================== 82 | vim: filetype=help expandtab textwidth=78 tabstop=8 norightleft foldenable foldlevel=0 : 83 | -------------------------------------------------------------------------------- /doc/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattn/vim-notification/a3a95668613dd29774c48394b54f89d8df3414bb/doc/screenshot.gif -------------------------------------------------------------------------------- /plugin/notification.vim: -------------------------------------------------------------------------------- 1 | let g:loaded_notification = 1 2 | --------------------------------------------------------------------------------