From b64b80a2bb525b2075ac82428d1c1e20771a277d Mon Sep 17 00:00:00 2001 From: Shidong Wang Date: Sun, 24 Oct 2021 16:25:43 +0800 Subject: [PATCH] refactor(chat): use bundle vim-chat plugin --- autoload/SpaceVim/layers/chat.vim | 6 +- bundle/vim-chat/.gitignore | 1 + bundle/vim-chat/.vintrc.yaml | 9 + bundle/vim-chat/README.md | 31 + bundle/vim-chat/autoload/chat.vim | 13 + bundle/vim-chat/autoload/chat/chatting.vim | 486 ++++++++++++ bundle/vim-chat/autoload/chat/debug.vim | 9 + bundle/vim-chat/autoload/chat/qq.vim | 814 +++++++++++++++++++++ bundle/vim-chat/autoload/chat/weixin.vim | 684 +++++++++++++++++ 9 files changed, 2050 insertions(+), 3 deletions(-) create mode 100644 bundle/vim-chat/.gitignore create mode 100644 bundle/vim-chat/.vintrc.yaml create mode 100644 bundle/vim-chat/README.md create mode 100644 bundle/vim-chat/autoload/chat.vim create mode 100644 bundle/vim-chat/autoload/chat/chatting.vim create mode 100644 bundle/vim-chat/autoload/chat/debug.vim create mode 100644 bundle/vim-chat/autoload/chat/qq.vim create mode 100644 bundle/vim-chat/autoload/chat/weixin.vim diff --git a/autoload/SpaceVim/layers/chat.vim b/autoload/SpaceVim/layers/chat.vim index 74f389caf..39854accf 100644 --- a/autoload/SpaceVim/layers/chat.vim +++ b/autoload/SpaceVim/layers/chat.vim @@ -7,9 +7,9 @@ "============================================================================= function! SpaceVim#layers#chat#plugins() abort - return [ - \ ['vim-chat/vim-chat',{ 'merged' : 0, 'loadconf' : 1}], - \ ] + return [ + \ [g:_spacevim_root_dir . 'bundle/vim-chat', {'merged' : 0, 'loadconf' : 1}], + \ ] endfunction let s:BASE64 = SpaceVim#api#import('data#base64') diff --git a/bundle/vim-chat/.gitignore b/bundle/vim-chat/.gitignore new file mode 100644 index 000000000..6e92f57d4 --- /dev/null +++ b/bundle/vim-chat/.gitignore @@ -0,0 +1 @@ +tags diff --git a/bundle/vim-chat/.vintrc.yaml b/bundle/vim-chat/.vintrc.yaml new file mode 100644 index 000000000..45a77de54 --- /dev/null +++ b/bundle/vim-chat/.vintrc.yaml @@ -0,0 +1,9 @@ +cmdargs: + # Checking more strictly + severity: style_problem + +policies: + ProhibitImplicitScopeVariable: + enabled: false + ProhibitAbbreviationOption: + enabled: false diff --git a/bundle/vim-chat/README.md b/bundle/vim-chat/README.md new file mode 100644 index 000000000..ab61395b5 --- /dev/null +++ b/bundle/vim-chat/README.md @@ -0,0 +1,31 @@ +## vim-chat + +chat in neovim and vim8 + + +### Install + +```viml +call dein#add('wsdjeg/vim-chat') +``` + +now you can chatting with me by `call chat#chatting#OpenMsgWin()`, insert `/help` for help message; + +### 微信及QQ聊天 + +#### 依赖 + +- [mojo-webqq](https://github.com/sjdy521/Mojo-Webqq) 和 [mojo-weixin](https://github.com/sjdy521/Mojo-Weixin):用于将QQ及微信协议转换成irc协议 +- IRC 依赖模块: `cpanm -v Mojo::IRC::Server::Chinese`, 详见: [irc.md](https://github.com/sjdy521/Mojo-Webqq/blob/master/IRC.md) +- [irssi](https://irssi.org/): irc 聊天客户端 +- [feh](https://feh.finalrewind.org/): 图片查看器,用于打开二维码 +- [neovim](https://github.com/neovim/neovim): vim8 的异步存在[问题](https://github.com/vim/vim/issues/1198),无法实现相应功能,故只能在neovim下使用 +- Linux: 目前仅在Linux下测试成功, Windows 下请直接使用QQ客户端 + +#### 启动 + +首先启动 QQ 服务器 `call chat#qq#start()`, 然后会自动弹出一个二维码,手机扫描下就可以登录了。neovim 默认使用 `Alt + x` 打开/关闭聊天窗口。 + +#### 效果图 + +![Markdown](http://i2.kiimg.com/1949/c18404d7afdc7f3a.gif) diff --git a/bundle/vim-chat/autoload/chat.vim b/bundle/vim-chat/autoload/chat.vim new file mode 100644 index 000000000..ada260a59 --- /dev/null +++ b/bundle/vim-chat/autoload/chat.vim @@ -0,0 +1,13 @@ +function! chat#startServer(name) abort + +endfunction + +function! chat#openWin(...) abort + if a:0 == 0 + call chat#chatting#OpenMsgWin() + else + if !empty(globpath(&rtp,'autoload/chat/' . a:1 .'.vim')) + call call('chat#' . a:1 . '#OpenMsgWin', []) + endif + endif +endfunction diff --git a/bundle/vim-chat/autoload/chat/chatting.vim b/bundle/vim-chat/autoload/chat/chatting.vim new file mode 100644 index 000000000..fd932eb7b --- /dev/null +++ b/bundle/vim-chat/autoload/chat/chatting.vim @@ -0,0 +1,486 @@ +scriptencoding utf-8 +let s:server_lib = get(g:, 'chatting_server_lib', fnamemodify('~/sources/Chatting/target/Chatting-1.0-SNAPSHOT.jar', ':p')) +let s:login_user = '' +let s:server_job_id = 0 +let s:client_job_id = 0 +let s:debug_log = [] +let s:chatting_commands = [] +let s:current_channel = '' +let s:last_channel = '' +let s:server_ip = get(g:, 'chatting_server_ip', 'perfi.wang') +let s:server_port = get(g:, 'chatting_server_port', 2013) +let s:vim8_ch_waittime = get(g:, 'chatting_ch_waittime', 100) +let s:close_windows_char = get(g:, 'chatting_close_win_char',"\") +let s:messages = [] +let s:opened_channels = [] +let s:unread_msg_num = {} +let s:st_sep = '' + +function! s:push_message(msg) abort + if type(a:msg) == type([]) + for m in a:msg + if !empty(m) + call s:hander_msg(m) + endif + endfor + else + if !empty(a:msg) + call s:hander_msg(a:msg) + endif + endif +endfunction + +function! s:hander_msg(msg) abort + let info = json_decode(a:msg) + call add(s:messages, info) + if info['type'] ==# 'onWindowChange' + let s:current_channel = info['name'] + if index(s:opened_channels, s:current_channel) == -1 + call add(s:opened_channels, s:current_channel) + endif + if s:current_channel !=# '' + call s:update_statusline() + endif + elseif info['type'] ==# 'group_message' + let n = get(s:unread_msg_num, info['group_name'], 0) + let n += 1 + if has_key(s:unread_msg_num, info['group_name']) + call remove(s:unread_msg_num, info['group_name']) + endif + call extend(s:unread_msg_num, {info['group_name'] : n}) + if s:current_channel !=# '' + call s:update_statusline() + endif + elseif info['type'] ==# 'info_message' + if info['context'] =~# 'you are logined as ' + let s:login_user = substitute(info['context'], 'you are logined as ', '', 'g') + endif + elseif info['type'] ==# 'onGetConnection' + let s:chatting_commands = split(info['commands'], ',') + endif + +endfunction + +function! chat#chatting#start() abort + if s:server_job_id == 0 + call s:log('startting server, server_lib is ' . s:server_lib . '(' . (empty(glob(s:server_lib)) ? 'no such file' : 'file exists' ). ')') + if has('nvim') + let s:server_job_id = jobstart(['java', '-cp', s:server_lib, 'com.wsdjeg.chat.Server'],{ + \ 'on_stdout' : function('s:server_handler'), + \ }) + elseif exists('*job#start') && !has('nvim') + let s:server_job_id = job#start(['java', '-cp', s:server_lib, 'com.wsdjeg.chat.Server'],{ + \ 'on_stdout' : function('s:server_handler'), + \ }) + endif + endif +endfunction + +function! s:server_handler(id, data, event) abort +endfunction + +function! s:client_handler(id, data, event) abort + if a:event ==# 'stdout' + call s:push_message(a:data) + call s:update_msg_screen() + elseif a:event ==# 'exit' + call s:log('client exit with code:' . a:data) + let s:client_job_id = 0 + endif +endfunction + +function! s:ch_callbakc(ch, msg) abort + call s:push_message(a:msg) + call s:update_msg_screen() +endfunction + +function! s:start_client() abort + if has('nvim') + if s:client_job_id == 0 + let s:client_job_id = jobstart(['java', '-cp', s:server_lib, 'com.wsdjeg.chat.Client', s:server_ip, s:server_port],{ + \ 'on_stdout' : function('s:client_handler'), + \ 'on_exit' : function('s:client_handler') + \ }) + call s:log('Server_lib:' . s:server_lib) + endif + else + let s:channel = ch_open(s:server_ip . ':' . s:server_port, + \ {'callback': function('s:ch_callbakc') ,'mode': 'nl', 'waittime': s:vim8_ch_waittime}) + call s:log('Client channel status:' . ch_status(s:channel)) + endif + call s:log('Client startting with server ip(' . s:server_ip . ') port(' . s:server_port . ')') +endfunction + +let s:name = '__Chatting__' +let s:c_base = '>>> ' +let s:c_begin = '' +let s:c_char = '' +let s:c_end = '' +let s:msg_win_opened = 0 +function! chat#chatting#OpenMsgWin() abort + if has('nvim') + if s:client_job_id == 0 + call s:start_client() + endif + else + if !exists('s:channel') || ch_status(s:channel) !=# 'open' + call s:start_client() + endif + endif + if bufwinnr(s:name) < 0 + if bufnr(s:name) != -1 + exe 'silent! botright split ' . '+b' . bufnr(s:name) + else + exe 'silent! botright split ' . s:name + endif + else + exec bufwinnr(s:name) . 'wincmd w' + endif + call s:windowsinit() + call s:init_hi() + setl modifiable + let s:msg_win_opened = 1 + if !empty(s:last_channel) + let s:current_channel = s:last_channel + endif + call s:update_msg_screen() + call s:echon() + while get(s:, 'quit_chating_win', 0) == 0 + let nr = getchar() + if nr !=# "\" && nr !=# "\" + let s:complete_input_history_num = [0,0] + endif + if nr != 9 + let s:complete_num = 0 + endif + if nr == 13 + call s:enter() + elseif nr ==# "\" || nr == 6 " 向右移动光标 + let s:c_begin = s:c_begin . s:c_char + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr == 21 " ctrl+u clean the message + let s:c_begin = '' + elseif nr == 9 " use complete str + if s:complete_num == 0 + let complete_base = s:c_begin + else + let s:c_begin = complete_base + endif + let s:c_begin = s:complete(complete_base, s:complete_num) + let s:complete_num += 1 + elseif nr == 11 " ctrl+k delete the chars from cursor to the end + let s:c_char = '' + let s:c_end = '' + elseif nr ==# "\" || nr ==# "\" + "+ 移动到左边一个聊天窗口 + call s:previous_channel() + elseif nr ==# "\" || nr ==# "\" + "+ 移动到右边一个聊天窗口 + call s:next_channel() + elseif nr ==# "\" || nr == 2 " 向左移动光标 + if s:c_begin !=# '' + let s:c_end = s:c_char . s:c_end + let s:c_char = matchstr(s:c_begin, '.$') + let s:c_begin = substitute(s:c_begin, '.$', '', 'g') + endif + elseif nr ==# "\" + let l = line('.') - winheight('$') + if l < 0 + exe 0 + else + exe l + endif + elseif nr ==# "\" + exe line('.') + winheight('$') + elseif nr ==# "\" || nr == 1 " + a 将光标移动到行首 + let s:c_end = substitute(s:c_begin . s:c_char . s:c_end, '^.', '', 'g') + let s:c_char = matchstr(s:c_begin, '^.') + let s:c_begin = '' + elseif nr ==# "\" || nr == 5 " + e 将光标移动到行末 + let s:c_begin = s:c_begin . s:c_char . s:c_end + let s:c_char = '' + let s:c_end = '' + elseif nr ==# s:close_windows_char + let s:quit_chating_win = 1 + elseif nr == 8 || nr ==# "\" " ctrl+h or delete last char + let s:c_begin = substitute(s:c_begin,'.$','','g') + elseif nr ==# "\" + if s:complete_input_history_num == [0,0] + let complete_input_history_base = s:c_begin + let s:c_char = '' + let s:c_end = '' + else + let s:c_begin = complete_input_history_base + endif + let s:complete_input_history_num[0] += 1 + let s:c_begin = s:complete_input_history(complete_input_history_base, s:complete_input_history_num) + elseif nr ==# "\" + if s:complete_input_history_num == [0,0] + let complete_input_history_base = s:c_begin + let s:c_char = '' + let s:c_end = '' + else + let s:c_begin = complete_input_history_base + endif + let s:complete_input_history_num[1] += 1 + let s:c_begin = s:complete_input_history(complete_input_history_base, s:complete_input_history_num) + else + let s:c_begin .= nr2char(nr) + endif + call s:echon() + endwhile + setl nomodifiable + exe 'bd ' . bufnr(s:name) + let s:quit_chating_win = 0 + let s:last_channel = s:current_channel + let s:current_channel = '' + let s:msg_win_opened = 0 + normal! : +endfunction + +function! s:update_msg_screen() abort + if s:msg_win_opened + normal! ggdG + for msg in s:messages + if msg['type'] ==# 'group_message' && msg['group_name'] ==# s:current_channel + call append(line('$'), '[' . msg['time'] . '] < ' . msg['sendder'] . ' > ' . msg['context']) + elseif msg['type'] ==# 'info_message' && msg['context'] !~# '^join channel :' + call append(line('$'), '[' . msg['time'] . '] ' . msg['context']) + elseif msg['type'] ==# 'user_message' + \ && ( + \ msg['sendder'] ==# s:current_channel + \ || + \ (msg['sendder'] ==# s:login_user && msg['receiver'] ==# s:current_channel) + \ ) + call append(line('$'), '[' . msg['time'] . '] < ' . msg['sendder'] . ' > ' . msg['context']) + endif + endfor + normal! gg + delete + normal! G + redraw + call s:echon() + endif +endfunction + +function! s:echon() abort + redraw! + echohl Comment | echon s:c_base + echohl None | echon s:c_begin + echohl Wildmenu | echon s:c_char + echohl None | echon s:c_end +endfunction + +fu! s:windowsinit() abort + " option + setl fileformat=unix + setl fileencoding=utf-8 + setl iskeyword=@,48-57,_ + setl noreadonly + setl buftype=nofile + setl bufhidden=wipe + setl noswapfile + setl nobuflisted + setl nolist + setl nonumber + setl norelativenumber + setl wrap + setl winfixwidth + setl winfixheight + setl textwidth=0 + setl nospell + setl nofoldenable + setl cursorline +endf + +function! s:log(msg) abort + let time = strftime('%H:%M:%S') + let msg = '[' . time . '] ' . string(a:msg) + call add(s:debug_log, msg) +endfunction + +function! s:debug() abort + tabnew + for line in s:debug_log + call append(line('$'), line) + endfor + nnoremap q :bd! +endfunction + + +let s:enter_history = [] +function! s:enter() abort + if s:c_begin . s:c_char . s:c_end ==# '/quit' + let s:quit_chating_win = 1 + let s:c_end = '' + let s:c_char = '' + let s:c_begin = '' + return + endif + if has('nvim') + if s:client_job_id != 0 + call jobsend(s:client_job_id, [s:c_begin . s:c_char . s:c_end, '']) + endif + else + call ch_sendraw(s:channel, s:c_begin . s:c_char . s:c_end ."\n") + endif + call add(s:enter_history, s:c_begin . s:c_char . s:c_end) + let s:c_end = '' + let s:c_char = '' + let s:c_begin = '' +endfunction + +let s:complete_num = 0 +function! s:complete(base,num) abort + if a:base =~# '^/[a-z]*$' + let rsl = filter(copy(s:chatting_commands), "v:val =~# a:base .'[^\ .]*'") + if len(rsl) > 0 + return rsl[a:num % len(rsl)] . ' ' + endif + endif + + return a:base + +endfunction +let s:complete_input_history_num = [0,0] +function! s:complete_input_history(base,num) abort + let results = filter(copy(s:enter_history), "v:val =~# '^' . a:base") + if len(results) > 0 + call add(results, a:base) + let index = ((len(results) - 1) - a:num[0] + a:num[1]) % len(results) + return results[index] + else + return a:base + endif +endfunction + +function! s:init_hi() abort + if get(s:, 'init_hi_done', 0) == 0 + " current channel + hi! ChattingHI1 ctermbg=003 ctermfg=Black guibg=#fabd2f guifg=#282828 + " channel with new msg + hi! ChattingHI2 ctermbg=005 ctermfg=Black guibg=#b16286 guifg=#282828 + " normal channel + hi! ChattingHI3 ctermbg=007 ctermfg=Black guibg=#8ec07c guifg=#282828 + " end + hi! ChattingHI4 ctermbg=243 guibg=#7c6f64 + " current channel + end + hi! ChattingHI5 guibg=#7c6f64 guifg=#fabd2f + " current channel + new msg channel + hi! ChattingHI6 guibg=#b16286 guifg=#fabd2f + " current channel + normal channel + hi! ChattingHI7 guibg=#8ec07c guifg=#fabd2f + " new msg channel + end + hi! ChattingHI8 guibg=#7c6f64 guifg=#b16286 + " new msg channel + current channel + hi! ChattingHI9 guibg=#fabd2f guifg=#b16286 + " new msg channel + normal channel + hi! ChattingHI10 guibg=#8ec07c guifg=#b16286 + " new msg channel + new msg channel + hi! ChattingHI11 guibg=#b16286 guifg=#b16286 + " normal channel + end + hi! ChattingHI12 guibg=#7c6f64 guifg=#8ec07c + " normal channel + normal channel + hi! ChattingHI13 guibg=#8ec07c guifg=#8ec07c + " normal channel + new msg channel + hi! ChattingHI14 guibg=#b16286 guifg=#8ec07c + " normal channel + current channel + hi! ChattingHI15 guibg=#fabd2f guifg=#8ec07c + let s:init_hi_done = 1 + endif +endfunction +function! s:update_statusline() abort + let st = '' + for ch in s:opened_channels + let ch = substitute(ch, ' ', '\ ', 'g') + if ch == s:current_channel + if has_key(s:unread_msg_num, s:current_channel) + call remove(s:unread_msg_num, s:current_channel) + endif + let st .= '%#ChattingHI1#[' . ch . ']' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#ChattingHI5#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + let st .= '%#ChattingHI6#' . s:st_sep + else + let st .= '%#ChattingHI7#' . s:st_sep + endif + else + let n = get(s:unread_msg_num, ch, 0) + if n > 0 + let st .= '%#ChattingHI2#[' . ch . '(' . n . 'new)]' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#ChattingHI8#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + \ && s:opened_channels[index(s:opened_channels, ch) + 1] !=# s:current_channel + let st .= '%#ChattingHI11#' . s:st_sep + elseif s:opened_channels[index(s:opened_channels, ch) + 1] ==# s:current_channel + let st .= '%#ChattingHI9#' . s:st_sep + else + let st .= '%#ChattingHI10#' . s:st_sep + endif + else + let st .= '%#ChattingHI3#[' . ch . ']' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#ChattingHI12#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + \ && s:opened_channels[index(s:opened_channels, ch) + 1] !=# s:current_channel + let st .= '%#ChattingHI14#' . s:st_sep + elseif s:opened_channels[index(s:opened_channels, ch) + 1] ==# s:current_channel + let st .= '%#ChattingHI15#' . s:st_sep + else + let st .= '%#ChattingHI13#' . s:st_sep + endif + endif + endif + endfor + let st .= '%#ChattingHI4# ' + exe 'set statusline=' . st +endfunction + +function! s:previous_channel() abort + let id = index(s:opened_channels, s:current_channel) + let id -= 1 + if id < 0 + let id = id + len(s:opened_channels) + endif + let s:current_channel = s:opened_channels[id] + if s:current_channel =~# '^#' + call s:send('/join ' . s:current_channel) + else + call s:send('/query ' . s:current_channel) + endif + call s:update_msg_screen() + call s:update_statusline() +endfunction + +function! s:next_channel() abort + let id = index(s:opened_channels, s:current_channel) + let id += 1 + if id > len(s:opened_channels) - 1 + let id = id - len(s:opened_channels) + endif + let s:current_channel = s:opened_channels[id] + if s:current_channel =~# '^#' + call s:send('/join ' . s:current_channel) + else + call s:send('/query ' . s:current_channel) + endif + call s:update_msg_screen() + call s:update_statusline() + +endfunction + +function! s:send(msg) abort + if has('nvim') + if s:client_job_id != 0 + call jobsend(s:client_job_id, [a:msg, '']) + endif + else + call ch_sendraw(s:channel, a:msg ."\n") + endif +endfunction + +call chat#debug#defind('chatting', function('s:debug')) + diff --git a/bundle/vim-chat/autoload/chat/debug.vim b/bundle/vim-chat/autoload/chat/debug.vim new file mode 100644 index 000000000..bdbe27b1e --- /dev/null +++ b/bundle/vim-chat/autoload/chat/debug.vim @@ -0,0 +1,9 @@ +let s:debug_func = {} + +function! chat#debug#defind(key, value) abort + call extend(s:debug_func, {a:key : a:value}) +endfunction + +function! chat#debug#getLog(key) abort + call call(s:debug_func[a:key],[]) +endfunction diff --git a/bundle/vim-chat/autoload/chat/qq.vim b/bundle/vim-chat/autoload/chat/qq.vim new file mode 100644 index 000000000..b99643ed2 --- /dev/null +++ b/bundle/vim-chat/autoload/chat/qq.vim @@ -0,0 +1,814 @@ +scriptencoding utf-8 +let s:save_cpo = &cpoptions +set cpoptions&vim + +let s:server_log = [] + +function! s:check_executable(exe) abort + if !executable(a:exe) + echohl WarningMsg + echo 'vim-chat need ' . a:exe . ' in your PATH' + echohl None + endif +endfunction + +let s:run_script = " + \ use Mojo::Webqq;\n + \ my $qq = " . get(g:, 'VimQQaccount', '279834419') . ";\n + \ my $client = Mojo::Webqq->new(qq=>$qq);\n + \ $client->load('ShowMsg');\n + \ $client->load('IRCShell',data=>{load_friend=>1,});\n + \ $client->log->handle(\*STDOUT);\n + \ $client->run();\n + \ " +let s:local_Mojo_dir = get(g:, 'local_Mojo_dir', '~/src/Mojo-Webqq/lib') +if isdirectory(s:local_Mojo_dir) + let s:run_script = "use lib '" . s:local_Mojo_dir . "'\n" . s:run_script +endif +let s:run_job_id = 0 +let s:irssi_job_id = 0 +let s:feh_code_id = 0 +let s:qq_channels = [] +let s:irssi_commands = ['/join', '/query', '/list', '/quit', '/msg', '/wc'] +let s:history = [] +let s:current_channel = '' +let s:last_channel = '' +let s:last_channel_input_methon = '' +let s:friends = [] " each item is ['channel','nickname'] +let s:input_history = [] +let s:complete_num = 0 +let s:complete_input_history_num = [0,0] +let s:opened_channels = [] +let s:irssi_log = [] +let s:unread_msg_num = {} +let s:st_sep = '' +let s:ch_input_method = [] " [ch_name, input_methon] 1:en 2:cn + +function! s:init_hi() abort + if get(s:, 'init_hi_done', 0) == 0 + " current channel + hi! VimQQ1 ctermbg=003 ctermfg=Black guibg=#fabd2f guifg=#282828 + " channel with new msg + hi! VimQQ2 ctermbg=005 ctermfg=Black guibg=#b16286 guifg=#282828 + " normal channel + hi! VimQQ3 ctermbg=007 ctermfg=Black guibg=#8ec07c guifg=#282828 + " end + hi! VimQQ4 ctermbg=243 guibg=#7c6f64 + " current channel + end + hi! VimQQ5 guibg=#7c6f64 guifg=#fabd2f + " current channel + new msg channel + hi! VimQQ6 guibg=#b16286 guifg=#fabd2f + " current channel + normal channel + hi! VimQQ7 guibg=#8ec07c guifg=#fabd2f + " new msg channel + end + hi! VimQQ8 guibg=#7c6f64 guifg=#b16286 + " new msg channel + current channel + hi! VimQQ9 guibg=#fabd2f guifg=#b16286 + " new msg channel + normal channel + hi! VimQQ10 guibg=#8ec07c guifg=#b16286 + " new msg channel + new msg channel + hi! VimQQ11 guibg=#b16286 guifg=#b16286 + " normal channel + end + hi! VimQQ12 guibg=#7c6f64 guifg=#8ec07c + " normal channel + normal channel + hi! VimQQ13 guibg=#8ec07c guifg=#8ec07c + " normal channel + new msg channel + hi! VimQQ14 guibg=#b16286 guifg=#8ec07c + " normal channel + current channel + hi! VimQQ15 guibg=#fabd2f guifg=#8ec07c + let s:init_hi_done = 1 + endif +endfunction + +function! s:jobstart(...) abort + if has('nvim') + if a:0 == 1 + return jobstart(a:1) + elseif a:0 == 2 + return jobstart(a:1, a:2) + endif + elseif exists('*job#start') && !has('nvim') + endif +endfunction + +function! s:jobstop(id) abort + if has('nvim') + call jobstop(a:id) + elseif exists('*job#stop') && !has('nvim') + endif +endfunction + +function! s:jobsend(id,data) abort + if has('nvim') + if type(a:data) == type('') + let data = [a:data, ''] + elseif type(a:data) == type([]) && a:data[-1] !=# '' + let data = a:data + [''] + else + let data = a:data + endif + call jobsend(a:id, data) + elseif exists('*job#send') && !has('nvim') + call job#send(a:id, a:data) + endif +endfunction + +function! s:feh_code(png) abort + call s:stop_feh() + let s:feh_code_id = s:jobstart(['feh', '--title', 'webqqcode', a:png]) +endfunction + +function! s:stop_feh() abort + if s:feh_code_id != 0 + call s:jobstop(s:feh_code_id) + let s:feh_code_id =0 + endif +endfunction + +function! s:irssi_handler(id, data, event) abort + if a:event ==# 'exit' + let s:irssi_job_id = 0 + elseif a:event ==# 'stderr' + call add(s:irssi_log, ['stderr', a:data]) + elseif a:event ==# 'stdout' + call add(s:irssi_log, ['stdout', a:data]) + endif +endfunction + +function! s:start_irssi() abort + if s:irssi_job_id == 0 + let argv = ['irssi','-c', '127.0.0.1', '-p', '6667'] + let s:irssi_job_id = s:jobstart(argv, { + \ 'on_stdout': function('s:irssi_handler'), + \ 'on_stderr': function('s:irssi_handler'), + \ 'on_exit': function('s:irssi_handler'), + \ }) + endif +endfunction + +function! s:handler_stdout_data(data) abort + if !empty(a:data) + call add(s:server_log, a:data) + endif + if match(a:data, '二维码已下载到本地\[ /tmp/mojo_webqq_qrcode_') != -1 + let png = matchstr(a:data, '/tmp/mojo_webqq_qrcode_default.png') + if !empty(png) + call s:feh_code(png) + endif + elseif matchstr(a:data, '帐号(\d*)登录成功') !=# '' + call s:stop_feh() + elseif matchstr(a:data,'频道\ #.*\ 已创建') !=# '' + let ch = matchstr(a:data,'#[^\ .]*') + if index(s:qq_channels, ch) == -1 + call add(s:qq_channels, ch) + endif + elseif matchstr(a:data, '\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[info\] \[.*\:虚拟用户\] 加入频道 #我的好友') !=# '' + " [16/10/31 20:06:09] [info] [nullptr:虚拟用户] 加入频道 #我的好友 + " [28:-42] + let friend = ['我的好友',a:data[28:-42]] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + elseif matchstr(a:data, '\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[群消息\]') !=# '' + " send:[16/10/22 18:26:58] [群消息] 我->Vim/exVim 开发讨论群 : 测试补全 + " start index 32 + if matchstr(a:data, '[^\ .]*->[^\ .]*\s\:\s') !=# '' + let idx1 = match(a:data, '->') + let idx2 = match(a:data, ' : ') + let msg = [ a:data[32:idx1-1], '#' . a:data[idx1+2:idx2-1], a:data[idx2+3:]] + let msg[1] = substitute(msg[1], '[\ !!@&]', '', 'g') + call add(s:history, msg) + let friend = [msg[1], msg[0]] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if msg[1] == s:current_channel + call s:update_msg_screen() + endif + " get:[16/10/22 18:26:58] [群消息] 灰灰|Vim/exVim 开发讨论群 : 测试补全 + elseif matchstr(a:data, '[^\ .]*|[^\ .]*\s\:\s') !=# '' + let idx1 = match(a:data, '|') + let idx2 = match(a:data, ' : ') + let msg = [ a:data[32:idx1-1], '#' .a:data[idx1+1:idx2-1], a:data[idx2+3:]] + let msg[1] = substitute(msg[1], '[\ !!@&]', '', 'g') + call add(s:history, msg) + let friend = [msg[1], msg[0]] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if msg[1] == s:current_channel + call s:update_msg_screen() + elseif index(s:opened_channels, msg[1]) != -1 && s:current_channel !=# msg[1] + let n = get(s:unread_msg_num, msg[1], 0) + let n += 1 + if has_key(s:unread_msg_num, msg[1]) + call remove(s:unread_msg_num, msg[1]) + endif + call extend(s:unread_msg_num, {msg[1] : n}) + if s:current_channel !=# '' + call s:update_statusline() + endif + endif + endif + elseif matchstr(a:data, '\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[好友消息\]') !=# '' + " send: [16/10/22 14:25:56] [好友消息] 我->老婆 : 1 + if matchstr(a:data, '[^\ .]*->[^\ .]*') !=# '' + let msg = split(matchstr(a:data, '[^\ .]*->[^\ .]*'), '->') + let f = msg[1] + let msg[1] = '' + call add(msg, substitute(a:data,'\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[好友消息\].*->[^\ .]*\ \:\ ','','g')) + call add(msg, f) + call add(s:history, msg) + let friend = ['我的好友',f] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if f == s:current_channel + call s:update_msg_screen() + endif + " get: [16/10/22 14:25:59] [好友消息] 老婆|我的好友 : 测试 + elseif matchstr(a:data, '[^\ .]*|[^\ .]*') !=# '' + let msg = split(matchstr(a:data, '[^\ .]*|[^\ .]*'), '|') + let f = msg[0] + let msg[1] = '' + call add(msg, substitute(a:data,'\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[好友消息\].*|[^\ .]*\ \:\ ','','g')) + call add(msg, f) + call add(s:history, msg) + let friend = ['我的好友',f] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if f == s:current_channel + call s:update_msg_screen() + elseif index(s:opened_channels, msg[3]) != -1 && s:current_channel !=# msg[3] + let n = get(s:unread_msg_num, msg[3], 0) + let n += 1 + if has_key(s:unread_msg_num, msg[3]) + call remove(s:unread_msg_num, msg[3]) + endif + call extend(s:unread_msg_num, {msg[3] : n}) + if s:current_channel !=# '' + call s:update_statusline() + endif + elseif index(s:opened_channels, msg[3]) == -1 + let n = get(s:unread_msg_num, msg[3], 0) + let n += 1 + if has_key(s:unread_msg_num, msg[3]) + call remove(s:unread_msg_num, msg[3]) + endif + call extend(s:unread_msg_num, {msg[3] : n}) + call add(s:opened_channels, msg[3]) + if s:current_channel !=# '' + call s:update_statusline() + endif + endif + endif + endif +endfunction +function! Test(str) abort + exe a:str +endfunction +function! s:start_handler(id, data, event) abort + if a:event ==# 'stdout' + if type(a:data) == type([]) + for a in a:data + call s:handler_stdout_data(a) + endfor + elseif type(a:data) == type('') + call s:handler_stdout_data(a:data) + else + endif + + elseif a:event ==# 'stderr' + elseif a:event ==# 'exit' + endif +endfunction + +function! chat#qq#start() abort + call s:check_executable('feh') + call s:check_executable('irssi') + call s:check_executable('perl') + let argv = ['perl', '-e', s:run_script] + if s:run_job_id == 0 + let s:run_job_id = s:jobstart(argv, { + \ 'on_stdout': function('s:start_handler'), + \ 'on_stderr': function('s:start_handler'), + \ 'on_exit': function('s:start_handler'), + \ }) + if s:run_job_id != 0 + echo 'qq server has been started!' + else + echo 'failed to start qq server!' + endif + else + echo 'qq server has been started!' + endif +endfunction + +function! s:send(...) abort + if a:0 > 0 + if s:irssi_job_id == 0 + call s:start_irssi() + endif + call s:jobsend(s:irssi_job_id, a:1) + endif +endfunction + +let s:name = '__VimQQ__' +let s:c_base = '>>>' +let s:c_begin = '' +let s:c_char = '' +let s:c_end = '' +function! chat#qq#OpenMsgWin() abort + if s:run_job_id == 0 + echohl WarningMsg + echo "qq server has not beed started, please use ':call chat#qq#start()'" + echohl NONE + return + endif + if bufwinnr('s:name') < 0 + if bufnr('s:name') != -1 + exe 'silent! botright split ' . '+b' . bufnr(s:name) + else + exe 'silent! botright split ' . s:name + endif + else + exec bufwinnr('s:name') . 'wincmd w' + endif + setl modifiable + call s:init_hi() + call s:windowsinit() + if s:last_channel !=# '' + let s:current_channel = s:last_channel + call s:update_statusline() + call s:update_msg_screen() + if s:last_channel_input_methon == 1 + call system('fcitx-remote -c') + elseif s:last_channel_input_methon == 2 + call system('fcitx-remote -o') + endif + endif + call s:echon() + while get(s:, 'quit_qq_win', 0) == 0 + let nr = getchar() + if nr != 9 + let s:complete_num = 0 + endif + if nr !=# "\" && nr !=# "\" + let s:complete_input_history_num = [0,0] + endif + if nr == 13 " 执行命令,或发送消息 + call s:parser_input(s:c_begin . s:c_char . s:c_end) + let s:c_begin = '' + let s:c_char = '' + let s:c_end = '' + elseif nr ==# "\" || nr ==# "\" + "+ 移动到左边一个聊天窗口 + call s:previous_channel() + elseif nr ==# "\" || nr ==# "\" + "+ 移动到右边一个聊天窗口 + call s:next_channel() + elseif nr ==# "\" || nr == 6 " 向右移动光标 + let s:c_begin = s:c_begin . s:c_char + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr ==# "\" || nr == 2 " 向左移动光标 + if s:c_begin !=# '' + let s:c_end = s:c_char . s:c_end + let s:c_char = matchstr(s:c_begin, '.$') + let s:c_begin = substitute(s:c_begin, '.$', '', 'g') + endif + elseif nr ==# "\" || nr == 1 " + a 将光标移动到行首 + let s:c_end = substitute(s:c_begin . s:c_char . s:c_end, '^.', '', 'g') + let s:c_char = matchstr(s:c_begin, '^.') + let s:c_begin = '' + elseif nr ==# "\" || nr == 5 " + e 将光标移动到行末 + let s:c_begin = s:c_begin . s:c_char . s:c_end + let s:c_char = '' + let s:c_end = '' + elseif nr ==# "\" "+x 关闭聊天窗口 + let s:quit_qq_win = 1 + let s:last_channel = s:current_channel + let s:current_channel = '' + if executable('fcitx-remote') + let s:last_channel_input_methon = system('fcitx-remote') + endif + elseif nr == 8 || nr ==# "\" " ctrl+h or delete last char + let s:c_begin = substitute(s:c_begin,'.$','','g') + elseif nr == 23 " ctrl+w delete last word + let s:c_begin = substitute(s:c_begin,'[^\ .*]\+\s*$','','g') + elseif nr == 11 " ctrl+k delete the chars from cursor to the end + let s:c_char = '' + let s:c_end = '' + elseif nr ==# "\" " Alt + f :按单词前移(右向) + if matchstr(s:c_end, '^\ *[^\ .]\+') !=# '' + let s:c_begin = s:c_begin . s:c_char . matchstr(s:c_end, '^\ *[^\ .]\+') + let s:c_end = substitute(s:c_end, '^\ *[^\ .]\+', '', 'g') + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + endif + elseif nr ==# "\" + let s:c_end = matchstr(s:c_begin, '[^\ .]\+\s*$') . s:c_char . s:c_end + let s:c_begin = substitute(s:c_begin, '[^\ .]\+\s*$', '', 'g') + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr ==# "\" " Alt + d 从光标处删除至词尾 + let s:c_end = s:c_char . s:c_end + let s:c_end = substitute(s:c_end, '^\s*[^\ .]*', '', 'g') + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr == 4 " ctrl+d delete the char under the cursor + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr == 21 " ctrl+u clean the message + let s:c_begin = '' + elseif nr == 9 " use complete str + if s:complete_num == 0 + let complete_base = s:c_begin + else + let s:c_begin = complete_base + endif + let s:c_begin = s:complete(complete_base, s:complete_num) + let s:complete_num += 1 + elseif nr == 47 " if type / and str is none, switch to en method + if s:c_begin ==# '' && s:c_char ==# '' && s:c_end ==# '' && executable('fcitx-remote') + call system('fcitx-remote -c') + endif + let s:c_begin .= nr2char(nr) + elseif nr ==# "\" + let l = line('.') - winheight('$') + if l < 0 + exe 0 + else + exe l + endif + elseif nr ==# "\" + exe line('.') + winheight('$') + elseif nr ==# "\" + if s:complete_input_history_num == [0,0] + let complete_input_history_base = s:c_begin + let s:c_char = '' + let s:c_end = '' + else + let s:c_begin = complete_input_history_base + endif + let s:complete_input_history_num[0] += 1 + let s:c_begin = s:complete_input_history(complete_input_history_base, s:complete_input_history_num) + elseif nr ==# "\" + if s:complete_input_history_num == [0,0] + let complete_input_history_base = s:c_begin + let s:c_char = '' + let s:c_end = '' + else + let s:c_begin = complete_input_history_base + endif + let s:complete_input_history_num[1] += 1 + let s:c_begin = s:complete_input_history(complete_input_history_base, s:complete_input_history_num) + else + let s:c_begin .= nr2char(nr) + endif + call s:echon() + endwhile + setl nomodifiable + exe 'bd ' . bufnr(s:name) + let s:quit_qq_win = 0 + normal! : + if executable('fcitx-remote') + call system('fcitx-remote -c') " switch 2 en + else + doautocmd InsertEnter + doautocmd InsertLeave + endif +endf + +function! s:complete(str, num) abort + if a:str =~# '^/[a-z]*$' + let rsl = filter(copy(s:irssi_commands), "v:val =~# a:str .'[^\ .]*'") + if len(rsl) > 0 + return rsl[a:num % len(rsl)] . ' ' + endif + elseif matchstr(a:str, '@[^\ .]$') !=# '' + let n_base = matchstr(a:str, '[^@^\ .]$') + let names = filter(deepcopy(s:friends), "v:val[0] ==# s:current_channel && v:val[1] =~# '^' . n_base") + if len(names) > 0 + return substitute(a:str, '[^@^\ .]$', '', 'g') . names[a:num % len(names)][1] + endif + elseif a:str =~# '/join\s\+#[^\ .]*$' || a:str =~# '^/join\s\+$' + let results = filter(deepcopy(s:qq_channels), "v:val =~# '" . substitute(a:str , '^/join\s\+', '', 'g') . "'") + if len(results) > 0 + return '/join ' . results[a:num % len(results)] + endif + elseif a:str =~# '^/query\s\+.\+' + let n_base = substitute(a:str, '^/query\s\+', '', 'g') + let names = filter(deepcopy(s:friends), "v:val[0] ==# '我的好友' && v:val[1] =~# '^' . n_base") + if len(names) > 0 + return '/query ' . names[a:num % len(names)][1] + endif + elseif a:str =~# '^/msg\s\+' + let n_base = substitute(a:str, '^/msg\s\+', '', 'g') + let res = [] " a list of string + if matchstr(n_base, '^.') ==# '#' + let res = filter(deepcopy(s:qq_channels), "v:val =~# '^' . n_base") + else + for name in filter(deepcopy(s:friends), "v:val[0] ==# '我的好友' && v:val[1] =~# '^' . n_base") + call add(res, name[1]) + endfor + let res += filter(deepcopy(s:qq_channels), "v:val =~# '^' . n_base") + endif + if len(res) > 0 + return '/msg ' . res[a:num % len(res)] . ' ' + endif + elseif index(s:qq_channels, s:current_channel) != -1 && a:str !~# '^/query' + let names = filter(deepcopy(s:friends), "v:val[0] ==# s:current_channel && v:val[1] =~# '^' . a:str") + if len(names) > 0 + return names[a:num % len(names)][1] . ': ' + endif + endif + return a:str +endfunction + +function! s:complete_input_history(str,num) abort + let results = filter(copy(s:input_history), "v:val =~# '^' . a:str") + if len(results) > 0 + call add(results, a:str) + let index = ((len(results) - 1) - a:num[0] + a:num[1]) % len(results) + return results[index] + else + return a:str + endif +endfunction + +function! s:echon() abort + redraw! + echohl Comment | echon s:c_base + echohl None | echon s:c_begin + echohl Wildmenu | echon s:c_char + echohl None | echon s:c_end +endfunction + +function! s:get_str_with_width(str,width) abort + let str = a:str + let result = '' + let tmp = '' + for i in range(strchars(str)) + let tmp .= matchstr(str, '^.') + if strwidth(tmp) > a:width + return result + else + let result = tmp + endif + let str = substitute(str, '^.', '', 'g') + endfor + return result +endfunction + +function! s:get_lines_with_width(str, width) abort + let str = a:str + let lines = [] + let line = '' + let tmp = '' + for i in range(strchars(str)) + let tmp .= matchstr(str, '^.') + if strwidth(tmp) > a:width + call add(lines, line) + let tmp = matchstr(str, '^.') + endif + let line = tmp + let str = substitute(str, '^.', '', 'g') + endfor + call add(lines, line) + return lines +endfunction + +function! s:update_msg_screen() abort + if index(s:qq_channels, s:current_channel) == -1 + let msgs = filter(deepcopy(s:history), 'len(v:val) == 4 && v:val[3] == s:current_channel') + let line = [line('.'),line('$')] + normal! ggdG + for msg in msgs + let name = s:get_str_with_width(msg[0], 13) " the width of the name must <= 13 + let message = s:get_lines_with_width(msg[2], winwidth('$') - 16) + let first_line = repeat(' ', 13 - strwidth(name)) . name . ' ' . nr2char(9474) . ' ' . message[0] + call append(line('$'), first_line) + if len(message) > 1 + for l in message[1:] + call append(line('$'), repeat(' ', 13) . ' ' . nr2char(9474) . ' ' . l) + endfor + endif + endfor + normal! gg + delete + if line[0] == line[1] + normal! G + else + exe line[0] + endif + else + let msgs = filter(deepcopy(s:history), 'v:val[1] == s:current_channel') + let line = [line('.'),line('$')] + normal! ggdG + for msg in msgs + let name = s:get_str_with_width(msg[0], 13) " the width of the name must <= 13 + let message = s:get_lines_with_width(msg[2], winwidth('$') - 16) + let first_line = repeat(' ', 13 - strwidth(name)) . name . ' ' . nr2char(9474) . ' ' . message[0] + call append(line('$'), first_line) + if len(message) > 1 + for l in message[1:] + call append(line('$'), repeat(' ', 13) . ' ' . nr2char(9474) . ' ' . l) + endfor + endif + endfor + normal! gg + delete + if line[0] == line[1] + normal! G + else + exe line[0] + endif + endif + redraw + call s:echon() +endfunction + +function! s:next_channel() abort + let id = index(s:opened_channels, s:current_channel) + let id += 1 + if id > len(s:opened_channels) - 1 + let id = id - len(s:opened_channels) + endif + let s:current_channel = s:opened_channels[id] + if s:current_channel =~# '^#' + call s:send('/join ' . s:current_channel) + else + call s:send('/query ' . s:current_channel) + endif + call s:update_msg_screen() + call s:update_statusline() +endfunction + +function! s:previous_channel() abort + let id = index(s:opened_channels, s:current_channel) + let id -= 1 + if id < 0 + let id = id + len(s:opened_channels) + endif + let s:current_channel = s:opened_channels[id] + if s:current_channel =~# '^#' + call s:send('/join ' . s:current_channel) + else + call s:send('/query ' . s:current_channel) + endif + call s:update_msg_screen() + call s:update_statusline() +endfunction + +function! s:parser_input(str) abort + if a:str !=# '' + call add(s:input_history, a:str) + endif + if a:str =~# '^/quit\s*$' + let s:quit_qq_win = 1 + let s:last_channel = s:current_channel + let s:current_channel = '' + if executable('fcitx-remote') + let s:last_channel_input_methon = system('fcitx-remote') + endif + elseif a:str ==# '/wc' + let cid = index(s:opened_channels, s:current_channel) + if cid == -1 + elseif cid == len(s:opened_channels) - 1 + call remove(s:opened_channels, cid) + call s:send('/WINDOW CLOSE') + let s:current_channel = get(s:opened_channels, cid - 1, '') + else + call remove(s:opened_channels, cid) + call s:send('/WINDOW CLOSE') + let s:current_channel = get(s:opened_channels, cid, '') + endif + call s:update_statusline() + call s:update_msg_screen() + redraw + elseif a:str =~# '^/join' + call s:send(a:str) + let s:current_channel = '#' . split(a:str, '#')[1] + if index(s:opened_channels, s:current_channel) == -1 + call add(s:opened_channels, s:current_channel) + endif + call s:update_statusline() + call s:update_msg_screen() + redraw + elseif a:str =~# '^/query\ \+.\+' + call s:send(a:str) + let s:current_channel = substitute(a:str, '^/query\ \+', '', 'g') + if index(s:opened_channels, s:current_channel) == -1 + call add(s:opened_channels, s:current_channel) + endif + call s:update_statusline() + call s:update_msg_screen() + redraw + elseif a:str =~# '^/msg\ \+' + call s:send(a:str) + let ch = matchstr(substitute(a:str, '^/msg\ \+', '', 'g'), '^[^\ .]*' ) + if index(s:opened_channels, ch) == -1 + if index(s:qq_channels, ch) != -1 || index(s:friends, ['我的好友', ch]) != -1 + call add(s:opened_channels, ch) + call s:update_statusline() + redraw + endif + endif + elseif a:str !~# '^/.*' + call s:send(a:str) + endif +endfunction + +function! s:update_statusline() abort + let st = '' + for ch in s:opened_channels + let ch = substitute(ch, ' ', '\ ', 'g') + if ch == s:current_channel + if has_key(s:unread_msg_num, s:current_channel) + call remove(s:unread_msg_num, s:current_channel) + endif + let st .= '%#VimQQ1#[' . ch . ']' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#VimQQ5#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + let st .= '%#VimQQ6#' . s:st_sep + else + let st .= '%#VimQQ7#' . s:st_sep + endif + else + let n = get(s:unread_msg_num, ch, 0) + if n > 0 + let st .= '%#VimQQ2#[' . ch . '(' . n . 'new)]' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#VimQQ8#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + \ && s:opened_channels[index(s:opened_channels, ch) + 1] !=# s:current_channel + let st .= '%#VimQQ11#' . s:st_sep + elseif s:opened_channels[index(s:opened_channels, ch) + 1] ==# s:current_channel + let st .= '%#VimQQ9#' . s:st_sep + else + let st .= '%#VimQQ10#' . s:st_sep + endif + else + let st .= '%#VimQQ3#[' . ch . ']' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#VimQQ12#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + \ && s:opened_channels[index(s:opened_channels, ch) + 1] !=# s:current_channel + let st .= '%#VimQQ14#' . s:st_sep + elseif s:opened_channels[index(s:opened_channels, ch) + 1] ==# s:current_channel + let st .= '%#VimQQ15#' . s:st_sep + else + let st .= '%#VimQQ13#' . s:st_sep + endif + endif + endif + endfor + let st .= '%#VimQQ4# ' + exe 'set statusline=' . st +endfunction + + +fu! s:windowsinit() abort + " option + setl fileformat=unix + setl fileencoding=utf-8 + setl iskeyword=@,48-57,_ + setl noreadonly + setl buftype=nofile + setl bufhidden=wipe + setl noswapfile + setl nobuflisted + setl nolist + setl nonumber + setl norelativenumber + setl wrap + setl winfixwidth + setl winfixheight + setl textwidth=0 + setl nospell + setl nofoldenable +endf + +" public api +function! chat#qq#ViewLog(...) abort + let nr = str2nr(get(a:000, 0, -1)) + tabnew +setl\ nobuflisted + nnoremap q :bd! + for msg in s:server_log + call append(line('$'), msg) + endfor + if nr != -1 && nr < len(s:server_log) + exe len(s:server_log) - nr + endif +endfunction + +" disable indentline in msg window +let g:indentLine_bufNameExclude = get(g:, 'indentLine_bufNameExclude', []) +if index(g:indentLine_bufNameExclude, s:name) == -1 + call add(g:indentLine_bufNameExclude, s:name) +endif +let &cpoptions = s:save_cpo +unlet s:save_cpo diff --git a/bundle/vim-chat/autoload/chat/weixin.vim b/bundle/vim-chat/autoload/chat/weixin.vim new file mode 100644 index 000000000..20395e290 --- /dev/null +++ b/bundle/vim-chat/autoload/chat/weixin.vim @@ -0,0 +1,684 @@ +scriptencoding utf-8 +let s:save_cpo = &cpoptions +set cpoptions&vim +" Debug +let s:server_log = [] + +let s:run_script = " + \ use local::lib;\n + \ use Mojo::Weixin;\n + \ my $client = Mojo::Weixin->new();\n + \ $client->load('ShowMsg');\n + \ $client->load('IRCShell',data=>{\n + \ listen=>[ {host=>'127.0.0.1',port=>6668},],\n + \ load_friend=>1,\n + \ });\n + \ $client->log->handle(\*STDOUT);\n + \ $client->run();\n + \ " +let s:run_job_id = 0 +let s:irssi_job_id = 0 +let s:feh_code_id = 0 +let s:channels = [] +let s:irssi_commands = ['/join', '/query', '/list', '/quit'] +let s:history = [] +let s:current_channel = '' +let s:last_channel = '' +let s:friends = [] " each item is ['channel','nickname'] +let s:input_history = [] +let s:complete_num = 0 +let s:complete_input_history_num = [0,0] +let s:opened_channels = [] +let s:irssi_log = [] +let s:unread_msg_num = {} +let s:st_sep = '' + +function! s:jobstart(...) abort + if has('nvim') + if a:0 == 1 + return jobstart(a:1) + elseif a:0 == 2 + return jobstart(a:1, a:2) + endif + elseif exists('*job#start') && !has('nvim') + endif +endfunction + +function! s:jobstop(id) abort + if has('nvim') + call jobstop(a:id) + elseif exists('*job#stop') && !has('nvim') + endif +endfunction + +function! s:jobsend(id,data) abort + if has('nvim') + if type(a:data) == type('') + let data = [a:data, ''] + elseif type(a:data) == type([]) && a:data[-1] !=# '' + let data = a:data + [''] + else + let data = a:data + endif + call jobsend(a:id, data) + elseif exists('*job#send') && !has('nvim') + call job#send(a:id, a:data) + endif +endfunction + +function! s:feh_code(png) abort + call s:stop_feh() + let s:feh_code_id = s:jobstart(['feh', a:png]) +endfunction + +function! s:stop_feh() abort + if s:feh_code_id != 0 + call s:jobstop(s:feh_code_id) + let s:feh_code_id =0 + endif +endfunction + +function! s:irssi_handler(id, data, event) abort + if a:event ==# 'exit' + let s:irssi_job_id = 0 + elseif a:event ==# 'stderr' + call add(s:irssi_log, ['stderr', a:data]) + elseif a:event ==# 'stdout' + call add(s:irssi_log, ['stdout', a:data]) + endif +endfunction + +function! s:start_irssi() abort + if s:irssi_job_id == 0 + let argv = ['irssi','-c', '127.0.0.1', '-p', '6668'] + let s:irssi_job_id = s:jobstart(argv, { + \ 'on_stdout': function('s:irssi_handler'), + \ 'on_stderr': function('s:irssi_handler'), + \ 'on_exit': function('s:irssi_handler'), + \ }) + endif +endfunction + +function! s:handler_stdout_data(data) abort + if !empty(a:data) + call add(s:server_log, a:data) + endif + if match(a:data, '二维码已下载到本地\[ /tmp/mojo_weixin_qrcode_default.jpg') != -1 + let png = matchstr(a:data, '/tmp/mojo_weixin_qrcode_default.jpg') + call s:feh_code(png) + elseif matchstr(a:data, '\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[info\] 微信登录成功') !=# '' + call s:stop_feh() + elseif matchstr(a:data,'频道\ #.*\ 已创建') !=# '' + let ch = matchstr(a:data,'#[^\ .]*') + if index(s:channels, ch) == -1 + call add(s:channels, ch) + endif + elseif matchstr(a:data, '\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[info\] \[.*\:虚拟用户\] 加入频道 #我的好友') !=# '' + " [16/10/31 20:06:09] [info] [nullptr:虚拟用户] 加入频道 #我的好友 + " [28:-42] + let friend = ['我的好友',a:data[28:-42]] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + elseif matchstr(a:data, '\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[群消息\]') !=# '' + " send:[16/10/22 18:26:58] [群消息] 我->Vim/exVim 开发讨论群 : 测试补全 + " start index 32 + if matchstr(a:data, '[^\ .]*->[^\ .]*') !=# '' + let idx1 = match(a:data, '->') + let idx2 = match(a:data, ' : ') + let msg = [ a:data[32:idx1-1], '#' . a:data[idx1+2:idx2-1], a:data[idx2+3:]] + let msg[1] = substitute(msg[1], '[\ !!@&]', '', 'g') + call add(s:history, msg) + let friend = [msg[1], msg[0]] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if msg[1] == s:current_channel + call s:update_msg_screen() + endif + " get:[16/10/22 18:26:58] [群消息] 灰灰|Vim/exVim 开发讨论群 : 测试补全 + elseif matchstr(a:data, '[^\ .]*|[^\ .]*') !=# '' + let idx1 = match(a:data, '|') + let idx2 = match(a:data, ' : ') + let msg = [ a:data[32:idx1-1], '#' .a:data[idx1+1:idx2-1], a:data[idx2+3:]] + let msg[1] = substitute(msg[1], '[\ !!@&]', '', 'g') + call add(s:history, msg) + let friend = [msg[1], msg[0]] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if msg[1] == s:current_channel + call s:update_msg_screen() + elseif index(s:opened_channels, msg[1]) != -1 && s:current_channel !=# msg[1] + let n = get(s:unread_msg_num, msg[1], 0) + let n += 1 + if has_key(s:unread_msg_num, msg[1]) + call remove(s:unread_msg_num, msg[1]) + endif + call extend(s:unread_msg_num, {msg[1] : n}) + if s:current_channel !=# '' + call s:update_statusline() + endif + endif + endif + elseif matchstr(a:data, '\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[好友消息\]') !=# '' + " send: [16/10/22 14:25:56] [好友消息] 我->老婆 : 1 + if matchstr(a:data, '[^\ .]*->[^\ .]*') !=# '' + let msg = split(matchstr(a:data, '[^\ .]*->[^\ .]*'), '->') + let f = msg[1] + let msg[1] = '' + call add(msg, substitute(a:data,'\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[好友消息\].*->[^\ .]*\ \:\ ','','g')) + call add(msg, f) + call add(s:history, msg) + let friend = ['我的好友',f] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if f == s:current_channel + call s:update_msg_screen() + endif + " get: [16/10/22 14:25:59] [好友消息] 老婆|我的好友 : 测试 + elseif matchstr(a:data, '[^\ .]*|[^\ .]*') !=# '' + let msg = split(matchstr(a:data, '[^\ .]*|[^\ .]*'), '|') + let f = msg[0] + let msg[1] = '' + call add(msg, substitute(a:data,'\[\d\d/\d\d/\d\d \d\d\:\d\d\:\d\d\] \[好友消息\].*|[^\ .]*\ \:\ ','','g')) + call add(msg, f) + call add(s:history, msg) + let friend = ['我的好友',f] + if index(s:friends, friend) == -1 + call add(s:friends, friend) + endif + if f == s:current_channel + call s:update_msg_screen() + elseif index(s:opened_channels, msg[3]) != -1 && s:current_channel !=# msg[3] + let n = get(s:unread_msg_num, msg[3], 0) + let n += 1 + if has_key(s:unread_msg_num, msg[3]) + call remove(s:unread_msg_num, msg[3]) + endif + call extend(s:unread_msg_num, {msg[3] : n}) + if s:current_channel !=# '' + call s:update_statusline() + endif + elseif index(s:opened_channels, msg[3]) == -1 + let n = get(s:unread_msg_num, msg[3], 0) + let n += 1 + if has_key(s:unread_msg_num, msg[3]) + call remove(s:unread_msg_num, msg[3]) + endif + call extend(s:unread_msg_num, {msg[3] : n}) + call add(s:opened_channels, msg[3]) + if s:current_channel !=# '' + call s:update_statusline() + endif + endif + endif + endif +endfunction +function! Test(str) abort + exe a:str +endfunction +function! s:start_handler(id, data, event) abort + if a:event ==# 'stdout' + if type(a:data) == type([]) + for a in a:data + call s:handler_stdout_data(a) + endfor + elseif type(a:data) == type('') + call s:handler_stdout_data(a:data) + else + endif + + elseif a:event ==# 'stderr' + elseif a:event ==# 'exit' + endif +endfunction + +function! chat#weixin#start() abort + let argv = ['perl', '-e', s:run_script] + if s:run_job_id == 0 + let s:run_job_id = s:jobstart(argv, { + \ 'on_stdout': function('s:start_handler'), + \ 'on_stderr': function('s:start_handler'), + \ 'on_exit': function('s:start_handler'), + \ }) + endif +endfunction + +function! s:send(...) abort + if a:0 > 0 + if s:irssi_job_id == 0 + call s:start_irssi() + endif + call s:jobsend(s:irssi_job_id, a:1) + endif +endfunction + +let s:name = '__VimWeixin__' +function! chat#weixin#OpenMsgWin() abort + if bufwinnr('s:name') < 0 + if bufnr('s:name') != -1 + exe 'silent! botright split ' . '+b' . bufnr(s:name) + else + exe 'silent! botright split ' . s:name + endif + else + exec bufwinnr('s:name') . 'wincmd w' + endif + setl modifiable + let s:c_base = '>>>' + let s:c_begin = '' + let s:c_char = '' + let s:c_end = '' + call s:windowsinit() + redraw + if s:last_channel !=# '' + if s:last_channel =~# '^#' + call s:send('/join ' . s:last_channel) + else + call s:send('/query ' . s:last_channel) + endif + let s:current_channel = s:last_channel + call s:update_statusline() + call s:update_msg_screen() + endif + call s:echon() + while get(s:, 'quit_msg_win', 0) == 0 + let nr = getchar() + if nr != 9 + let s:complete_num = 0 + endif + if nr !=# "\" && nr !=# "\" + let s:complete_input_history_num = [0,0] + endif + if nr == 13 " 执行命令,或发送消息 + call s:parser_input(s:c_begin . s:c_char . s:c_end) + let s:c_begin = '' + let s:c_char = '' + let s:c_end = '' + elseif nr ==# "\" "+ 移动到左边一个聊天窗口 + call s:previous_channel() + elseif nr ==# "\" "+ 移动到右边一个聊天窗口 + call s:next_channel() + elseif nr ==# "\" || nr == 6 " 向右移动光标 + let s:c_begin = s:c_begin . s:c_char + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr ==# "\" || nr == 2 " 向左移动光标 + if s:c_begin !=# '' + let s:c_end = s:c_char . s:c_end + let s:c_char = matchstr(s:c_begin, '.$') + let s:c_begin = substitute(s:c_begin, '.$', '', 'g') + endif + elseif nr ==# "\" || nr == 1 " 将光标移动到行首 + let s:c_end = substitute(s:c_begin . s:c_char . s:c_end, '^.', '', 'g') + let s:c_char = matchstr(s:c_begin, '^.') + let s:c_begin = '' + elseif nr ==# "\" || nr == 5 " 将光标移动到行末 + let s:c_begin = s:c_begin . s:c_char . s:c_end + let s:c_char = '' + let s:c_end = '' + elseif nr ==# "\" "+x 关闭聊天窗口 + let s:quit_msg_win = 1 + let s:last_channel = s:current_channel + let s:current_channel = '' + elseif nr == 8 || nr ==# "\" " ctrl+h or delete last char + let s:c_begin = substitute(s:c_begin,'.$','','g') + elseif nr == 23 " ctrl+w delete last word + let s:c_begin = substitute(s:c_begin,'[^\ .*]\+\s*$','','g') + elseif nr == 11 " ctrl+k delete the chars from cursor to the end + let s:c_char = '' + let s:c_end = '' + elseif nr ==# "\" " Alt + f :按单词前移(右向) + if matchstr(s:c_end, '^\ *[^\ .]\+') !=# '' + let s:c_begin = s:c_begin . s:c_char . matchstr(s:c_end, '^\ *[^\ .]\+') + let s:c_end = substitute(s:c_end, '^\ *[^\ .]\+', '', 'g') + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + endif + elseif nr ==# "\" + let s:c_end = matchstr(s:c_begin, '[^\ .]\+\s*$') . s:c_char . s:c_end + let s:c_begin = substitute(s:c_begin, '[^\ .]\+\s*$', '', 'g') + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr ==# "\" " Alt + d 从光标处删除至词尾 + let s:c_end = s:c_char . s:c_end + let s:c_end = substitute(s:c_end, '^\s*[^\ .]*', '', 'g') + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr == 4 " ctrl+d delete the char under the cursor + let s:c_char = matchstr(s:c_end, '^.') + let s:c_end = substitute(s:c_end, '^.', '', 'g') + elseif nr == 21 " ctrl+u clean the message + let s:c_begin = '' + elseif nr == 9 " use complete str + if s:complete_num == 0 + let complete_base = s:c_begin + else + let s:c_begin = complete_base + endif + let s:c_begin = s:complete(complete_base, s:complete_num) + let s:complete_num += 1 + elseif nr == 47 " if type / and str is none, switch to en method + if s:c_begin ==# '' && s:c_char ==# '' && s:c_end ==# '' && executable('fcitx-remote') + call system('fcitx-remote -c') + endif + let s:c_begin .= nr2char(nr) + elseif nr ==# "\" + let l = line('.') - winheight('$') + if l < 0 + exe 0 + else + exe l + endif + elseif nr ==# "\" + exe line('.') + winheight('$') + elseif nr ==# "\" + if s:complete_input_history_num == [0,0] + let complete_input_history_base = s:c_begin + let s:c_char = '' + let s:c_end = '' + else + let s:c_begin = complete_input_history_base + endif + let s:complete_input_history_num[0] += 1 + let s:c_begin = s:complete_input_history(complete_input_history_base, s:complete_input_history_num) + elseif nr ==# "\" + if s:complete_input_history_num == [0,0] + let complete_input_history_base = s:c_begin + let s:c_char = '' + let s:c_end = '' + else + let s:c_begin = complete_input_history_base + endif + let s:complete_input_history_num[1] += 1 + let s:c_begin = s:complete_input_history(complete_input_history_base, s:complete_input_history_num) + else + let s:c_begin .= nr2char(nr) + endif + call s:echon() + endwhile + setl nomodifiable + exe 'bd ' . bufnr(s:name) + let s:quit_msg_win = 0 + normal! : + if executable('fcitx-remote') + call system('fcitx-remote -c') " switch 2 en + else + doautocmd InsertEnter + doautocmd InsertLeave + endif +endf + +function! s:complete(str, num) abort + if a:str =~# '^/[a-z]*$' + let rsl = filter(copy(s:irssi_commands), "v:val =~# a:str .'[^\ .]*'") + if len(rsl) > 0 + return rsl[a:num % len(rsl)] . ' ' + else + return a:str + endif + elseif a:str =~# '/join\s\+#\?$' + if len(s:channels) > 0 + return a:str[-1:] ==# '#' ? a:str[:-2] . s:channels[0] : a:str . s:channels[0] + else + return a:str + endif + elseif a:str =~# '/join\s\+#.\+$' + let results = filter(deepcopy(s:channels), "v:val =~# '" . substitute(a:str , '^/join\s\+', '', 'g') . "'") + if len(results) > 0 + return '/join ' . results[a:num % len(results)] + endif + return a:str + elseif index(s:channels, s:current_channel) != -1 && a:str !~# '^/query' + let names = filter(deepcopy(s:friends), "v:val[0] ==# s:current_channel && v:val[1] =~# '^' . a:str") + if len(names) > 0 + return names[a:num % len(names)][1] . ': ' + endif + return a:str + elseif a:str =~# '^/query\s\+.\+' + let n_base = substitute(a:str, '^/query\s\+', '', 'g') + let names = filter(deepcopy(s:friends), "v:val[0] ==# '我的好友' && v:val[1] =~# '^' . n_base") + if len(names) > 0 + return '/query ' . names[a:num % len(names)][1] + endif + return a:str + else + return a:str + endif +endfunction + +function! s:complete_input_history(str,num) abort + let results = filter(copy(s:input_history), "v:val =~# '^' . a:str") + if len(results) > 0 + call add(results, a:str) + let index = ((len(results) - 1) - a:num[0] + a:num[1]) % len(results) + return results[index] + else + return a:str + endif +endfunction + +function! s:echon() abort + redraw + echohl Comment | echon s:c_base + echohl None | echon s:c_begin + echohl Wildmenu | echon s:c_char + echohl None | echon s:c_end +endfunction + +function! s:update_msg_screen() abort + if index(s:channels, s:current_channel) == -1 + let msgs = filter(deepcopy(s:history), 'len(v:val) == 4 && v:val[3] == s:current_channel') + let line = [line('.'),line('$')] + normal! ggdG + for msg in msgs + call append(line('$'), msg[0] . repeat(' ', 13 - strwidth(msg[0])) . ' | ' . msg[2]) + endfor + if line[0] == line[1] + normal! G + else + exe line[0] + endif + else + let msgs = filter(deepcopy(s:history), 'v:val[1] == s:current_channel') + let line = [line('.'),line('$')] + normal! ggdG + for msg in msgs + call append(line('$'), msg[0] . repeat(' ', 13 - strwidth(msg[0])) . ' | ' . msg[2]) + endfor + if line[0] == line[1] + normal! G + else + exe line[0] + endif + endif + redraw + call s:echon() +endfunction + +function! s:next_channel() abort + let id = index(s:opened_channels, s:current_channel) + let id += 1 + if id > len(s:opened_channels) - 1 + let id = id - len(s:opened_channels) + endif + let s:current_channel = s:opened_channels[id] + if s:current_channel =~# '^#' + call s:send('/join ' . s:current_channel) + else + call s:send('/query ' . s:current_channel) + endif + call s:update_msg_screen() + call s:update_statusline() +endfunction + +function! s:previous_channel() abort + let id = index(s:opened_channels, s:current_channel) + let id -= 1 + if id < 0 + let id = id + len(s:opened_channels) + endif + let s:current_channel = s:opened_channels[id] + if s:current_channel =~# '^#' + call s:send('/join ' . s:current_channel) + else + call s:send('/query ' . s:current_channel) + endif + call s:update_msg_screen() + call s:update_statusline() +endfunction + +function! s:parser_input(str) abort + if a:str !=# '' + call add(s:input_history, a:str) + endif + if a:str =~# '^/quit\s*$' + let s:quit_msg_win = 1 + let s:last_channel = s:current_channel + let s:current_channel = '' + elseif a:str ==# '/wc' + let cid = index(s:opened_channels, s:current_channel) + if cid == -1 + elseif cid == len(s:opened_channels) - 1 + call remove(s:opened_channels, cid) + call s:send('/WINDOW CLOSE') + let s:current_channel = get(s:opened_channels, cid - 1, '') + else + call remove(s:opened_channels, cid) + call s:send('/WINDOW CLOSE') + let s:current_channel = get(s:opened_channels, cid, '') + endif + call s:update_statusline() + call s:update_msg_screen() + redraw + elseif a:str =~# '^/join' + call s:send(a:str) + let s:current_channel = '#' . split(a:str, '#')[1] + if index(s:opened_channels, s:current_channel) == -1 + call add(s:opened_channels, s:current_channel) + endif + call s:update_statusline() + call s:update_msg_screen() + redraw + elseif a:str =~# '^/query\ \+.\+' + call s:send(a:str) + let s:current_channel = substitute(a:str, '^/query\ \+', '', 'g') + if index(s:opened_channels, s:current_channel) == -1 + call add(s:opened_channels, s:current_channel) + endif + call s:update_statusline() + call s:update_msg_screen() + redraw + elseif a:str !~# '^/.*' + call s:send(a:str) + endif +endfunction + +function! s:update_statusline() abort + " current channel + hi! VimQQ1 ctermbg=003 ctermfg=Black guibg=#fabd2f guifg=#282828 + " channel with new msg + hi! VimQQ2 ctermbg=005 ctermfg=Black guibg=#b16286 guifg=#282828 + " normal channel + hi! VimQQ3 ctermbg=007 ctermfg=Black guibg=#8ec07c guifg=#282828 + " end + hi! VimQQ4 ctermbg=243 guibg=#7c6f64 + " current channel + end + hi! VimQQ5 guibg=#7c6f64 guifg=#fabd2f + " current channel + new msg channel + hi! VimQQ6 guibg=#b16286 guifg=#fabd2f + " current channel + normal channel + hi! VimQQ7 guibg=#8ec07c guifg=#fabd2f + " new msg channel + end + hi! VimQQ8 guibg=#7c6f64 guifg=#b16286 + " new msg channel + current channel + hi! VimQQ9 guibg=#fabd2f guifg=#b16286 + " new msg channel + normal channel + hi! VimQQ10 guibg=#8ec07c guifg=#b16286 + " new msg channel + new msg channel + hi! VimQQ11 guibg=#b16286 guifg=#b16286 + " normal channel + end + hi! VimQQ12 guibg=#7c6f64 guifg=#8ec07c + " normal channel + normal channel + hi! VimQQ13 guibg=#8ec07c guifg=#8ec07c + " normal channel + new msg channel + hi! VimQQ14 guibg=#b16286 guifg=#8ec07c + " normal channel + current channel + hi! VimQQ15 guibg=#fabd2f guifg=#8ec07c + + let st = '' + for ch in s:opened_channels + let ch = substitute(ch, ' ', '\ ', 'g') + if ch == s:current_channel + if has_key(s:unread_msg_num, s:current_channel) + call remove(s:unread_msg_num, s:current_channel) + endif + let st .= '%#VimQQ1#[' . ch . ']' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#VimQQ5#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + let st .= '%#VimQQ6#' . s:st_sep + else + let st .= '%#VimQQ7#' . s:st_sep + endif + else + let n = get(s:unread_msg_num, ch, 0) + if n > 0 + let st .= '%#VimQQ2#[' . ch . '(' . n . 'new)]' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#VimQQ8#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + \ && s:opened_channels[index(s:opened_channels, ch) + 1] !=# s:current_channel + let st .= '%#VimQQ11#' . s:st_sep + elseif s:opened_channels[index(s:opened_channels, ch) + 1] ==# s:current_channel + let st .= '%#VimQQ9#' . s:st_sep + else + let st .= '%#VimQQ10#' . s:st_sep + endif + else + let st .= '%#VimQQ3#[' . ch . ']' + if index(s:opened_channels, ch) == len(s:opened_channels) - 1 + let st .= '%#VimQQ12#' . s:st_sep + elseif get(s:unread_msg_num, s:opened_channels[index(s:opened_channels, ch) + 1], 0) > 0 + \ && s:opened_channels[index(s:opened_channels, ch) + 1] !=# s:current_channel + let st .= '%#VimQQ14#' . s:st_sep + elseif s:opened_channels[index(s:opened_channels, ch) + 1] ==# s:current_channel + let st .= '%#VimQQ15#' . s:st_sep + else + let st .= '%#VimQQ13#' . s:st_sep + endif + endif + endif + endfor + let st .= '%#VimQQ4# ' + exe 'set statusline=' . st + redraw + call s:echon() +endfunction + + +fu! s:windowsinit() abort + " option + setl fileformat=unix + setl fileencoding=utf-8 + setl iskeyword=@,48-57,_ + setl noreadonly + setl buftype=nofile + setl bufhidden=wipe + setl noswapfile + setl nobuflisted + setl nolist + setl nonumber + setl wrap + setl winfixwidth + setl winfixheight + setl textwidth=0 + setl nospell + setl nofoldenable +endf + + +let &cpoptions = s:save_cpo +unlet s:save_cpo