2013-06-25 07:57:15 +00:00
|
|
|
" ============================================================================
|
|
|
|
" File: wakatime.vim
|
2013-07-02 09:24:04 +00:00
|
|
|
" Description: Automatic time tracking for Vim.
|
2013-09-05 05:32:20 +00:00
|
|
|
" Maintainer: WakaTime <support@wakatime.com>
|
2014-03-13 00:32:05 +00:00
|
|
|
" License: BSD, see LICENSE.txt for more details.
|
2014-03-14 20:35:11 +00:00
|
|
|
" Website: https://wakatime.com/
|
2013-06-25 07:57:15 +00:00
|
|
|
" ============================================================================
|
|
|
|
|
2019-03-31 02:20:54 +00:00
|
|
|
let s:VERSION = '7.1.4'
|
2013-07-09 16:55:22 +00:00
|
|
|
|
2013-06-26 04:09:52 +00:00
|
|
|
|
2013-06-25 07:57:15 +00:00
|
|
|
" Init {{{
|
|
|
|
|
2013-07-02 09:24:04 +00:00
|
|
|
" Check Vim version
|
|
|
|
if v:version < 700
|
|
|
|
echoerr "This plugin requires vim >= 7."
|
|
|
|
finish
|
2013-06-25 07:57:15 +00:00
|
|
|
endif
|
2013-07-02 09:24:04 +00:00
|
|
|
|
2017-04-13 06:02:57 +00:00
|
|
|
" Use constants for truthy check to improve readability
|
|
|
|
let s:true = 1
|
|
|
|
let s:false = 0
|
|
|
|
|
2013-07-02 09:24:04 +00:00
|
|
|
" Only load plugin once
|
|
|
|
if exists("g:loaded_wakatime")
|
|
|
|
finish
|
|
|
|
endif
|
2017-04-13 06:02:57 +00:00
|
|
|
let g:loaded_wakatime = s:true
|
2013-06-25 07:57:15 +00:00
|
|
|
|
2013-07-02 09:24:04 +00:00
|
|
|
" Backup & Override cpoptions
|
|
|
|
let s:old_cpo = &cpo
|
|
|
|
set cpo&vim
|
2013-06-25 07:57:15 +00:00
|
|
|
|
2017-05-19 05:07:17 +00:00
|
|
|
" Backup wildignore before clearing it to prevent conflicts with expand()
|
|
|
|
let s:wildignore = &wildignore
|
|
|
|
if s:wildignore != ""
|
|
|
|
set wildignore=""
|
|
|
|
endif
|
|
|
|
|
2015-12-25 08:34:44 +00:00
|
|
|
" Script Globals
|
2017-04-14 06:04:40 +00:00
|
|
|
let s:home = expand("$WAKATIME_HOME")
|
|
|
|
if s:home == '$WAKATIME_HOME'
|
|
|
|
let s:home = expand("$HOME")
|
|
|
|
endif
|
2018-04-05 16:24:11 +00:00
|
|
|
let s:cli_location = substitute(substitute(expand("<sfile>:p:h"), '\', '/', 'g'), '/plugin$', '', '') . '/packages/wakatime/cli.py'
|
2017-04-14 06:04:40 +00:00
|
|
|
let s:config_file = s:home . '/.wakatime.cfg'
|
2017-04-18 08:57:35 +00:00
|
|
|
let s:default_configs = ['[settings]', 'debug = false', 'hidefilenames = false', 'ignore =', ' COMMIT_EDITMSG$', ' PULLREQ_EDITMSG$', ' MERGE_MSG$', ' TAG_EDITMSG$']
|
2017-04-14 06:04:40 +00:00
|
|
|
let s:data_file = s:home . '/.wakatime.data'
|
2017-04-27 01:57:33 +00:00
|
|
|
let s:has_reltime = has('reltime') && localtime() - 1 < split(split(reltimestr(reltime()))[0], '\.')[0]
|
2017-04-13 06:02:57 +00:00
|
|
|
let s:config_file_already_setup = s:false
|
2017-04-13 07:13:48 +00:00
|
|
|
let s:debug_mode_already_setup = s:false
|
2017-10-04 15:23:07 +00:00
|
|
|
let s:is_debug_on = s:false
|
2017-04-13 06:02:57 +00:00
|
|
|
let s:local_cache_expire = 10 " seconds between reading s:data_file
|
2017-04-22 20:40:54 +00:00
|
|
|
let s:last_heartbeat = {'last_activity_at': 0, 'last_heartbeat_at': 0, 'file': ''}
|
2017-04-22 09:09:01 +00:00
|
|
|
let s:heartbeats_buffer = []
|
2018-10-07 02:57:53 +00:00
|
|
|
let s:send_buffer_seconds = 30 " seconds between sending buffered heartbeats
|
2017-05-17 04:00:44 +00:00
|
|
|
let s:last_sent = localtime()
|
2017-10-17 15:08:08 +00:00
|
|
|
let s:has_async = has('patch-7.4-2344') && exists('*job_start')
|
2017-10-05 01:59:43 +00:00
|
|
|
let s:nvim_async = exists('*jobstart')
|
2015-05-01 23:08:02 +00:00
|
|
|
|
2017-09-04 17:13:34 +00:00
|
|
|
|
|
|
|
function! s:Init()
|
|
|
|
|
|
|
|
" For backwards compatibility, rename wakatime.conf to wakatime.cfg
|
|
|
|
if !filereadable(s:config_file)
|
|
|
|
if filereadable(expand("$HOME/.wakatime"))
|
|
|
|
exec "silent !mv" expand("$HOME/.wakatime") expand("$HOME/.wakatime.conf")
|
|
|
|
endif
|
|
|
|
if filereadable(expand("$HOME/.wakatime.conf"))
|
|
|
|
if !filereadable(s:config_file)
|
|
|
|
let contents = ['[settings]'] + readfile(expand("$HOME/.wakatime.conf"), '')
|
|
|
|
call writefile(contents, s:config_file)
|
|
|
|
call delete(expand("$HOME/.wakatime.conf"))
|
|
|
|
endif
|
2015-05-01 23:08:02 +00:00
|
|
|
endif
|
2013-12-13 15:03:09 +00:00
|
|
|
endif
|
2013-06-26 05:02:59 +00:00
|
|
|
|
2017-09-04 17:13:34 +00:00
|
|
|
" Set default python binary location
|
|
|
|
if !exists("g:wakatime_PythonBinary")
|
|
|
|
let g:wakatime_PythonBinary = 'python'
|
|
|
|
endif
|
2015-02-13 03:19:40 +00:00
|
|
|
|
2017-09-04 17:13:34 +00:00
|
|
|
" Set default heartbeat frequency in minutes
|
|
|
|
if !exists("g:wakatime_HeartbeatFrequency")
|
|
|
|
let g:wakatime_HeartbeatFrequency = 2
|
|
|
|
endif
|
2013-08-12 10:30:17 +00:00
|
|
|
|
2017-09-04 17:13:34 +00:00
|
|
|
" Get legacy g:wakatime_ScreenRedraw setting
|
|
|
|
let s:redraw_setting = 'auto'
|
|
|
|
if exists("g:wakatime_ScreenRedraw") && g:wakatime_ScreenRedraw
|
|
|
|
let s:redraw_setting = 'enabled'
|
|
|
|
endif
|
|
|
|
|
|
|
|
" Get redraw setting from wakatime.cfg file
|
|
|
|
if s:GetIniSetting('settings', 'vi_redraw') != ''
|
|
|
|
if s:GetIniSetting('settings', 'vi_redraw') == 'enabled'
|
|
|
|
let s:redraw_setting = 'enabled'
|
|
|
|
endif
|
|
|
|
if s:GetIniSetting('settings', 'vi_redraw') == 'auto'
|
|
|
|
let s:redraw_setting = 'auto'
|
|
|
|
endif
|
|
|
|
if s:GetIniSetting('settings', 'vi_redraw') == 'disabled'
|
|
|
|
let s:redraw_setting = 'disabled'
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
|
2017-10-04 15:23:07 +00:00
|
|
|
" Buffering heartbeats disabled in Windows, unless have async support
|
2017-10-05 01:59:43 +00:00
|
|
|
let s:buffering_heartbeats_enabled = s:has_async || s:nvim_async || !s:IsWindows()
|
2017-10-04 15:23:07 +00:00
|
|
|
|
2017-09-04 17:13:34 +00:00
|
|
|
endfunction
|
2017-04-25 14:56:04 +00:00
|
|
|
|
2013-06-25 07:57:15 +00:00
|
|
|
" }}}
|
|
|
|
|
2013-06-26 04:09:52 +00:00
|
|
|
|
2013-06-25 07:57:15 +00:00
|
|
|
" Function Definitions {{{
|
|
|
|
|
2015-05-01 23:08:02 +00:00
|
|
|
function! s:StripWhitespace(str)
|
|
|
|
return substitute(a:str, '^\s*\(.\{-}\)\s*$', '\1', '')
|
|
|
|
endfunction
|
2017-02-14 07:39:06 +00:00
|
|
|
|
2015-01-20 04:36:23 +00:00
|
|
|
function! s:SetupConfigFile()
|
2015-05-01 23:08:02 +00:00
|
|
|
if !s:config_file_already_setup
|
|
|
|
|
2015-01-20 04:36:23 +00:00
|
|
|
" Create config file if does not exist
|
2015-05-01 23:08:02 +00:00
|
|
|
if !filereadable(s:config_file)
|
2017-04-18 08:57:35 +00:00
|
|
|
call writefile(s:default_configs, s:config_file)
|
|
|
|
endif
|
2015-05-03 16:52:49 +00:00
|
|
|
|
|
|
|
" Make sure config file has api_key
|
2017-04-18 08:57:35 +00:00
|
|
|
let found_api_key = s:false
|
|
|
|
if s:GetIniSetting('settings', 'api_key') != '' || s:GetIniSetting('settings', 'apikey') != ''
|
|
|
|
let found_api_key = s:true
|
|
|
|
endif
|
|
|
|
if !found_api_key
|
2017-04-21 15:00:06 +00:00
|
|
|
call s:PromptForApiKey()
|
2017-04-18 08:57:35 +00:00
|
|
|
echo "[WakaTime] Setup complete! Visit https://wakatime.com to view your coding activity."
|
2015-01-20 04:36:23 +00:00
|
|
|
endif
|
2015-05-01 23:08:02 +00:00
|
|
|
|
2017-04-13 06:02:57 +00:00
|
|
|
let s:config_file_already_setup = s:true
|
2015-01-20 04:36:23 +00:00
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
2017-04-13 07:13:48 +00:00
|
|
|
function! s:SetupDebugMode()
|
|
|
|
if !s:debug_mode_already_setup
|
|
|
|
if s:GetIniSetting('settings', 'debug') == 'true'
|
2017-10-04 15:23:07 +00:00
|
|
|
let s:is_debug_on = s:true
|
2017-04-13 07:13:48 +00:00
|
|
|
else
|
2017-10-04 15:23:07 +00:00
|
|
|
let s:is_debug_on = s:false
|
2017-04-13 07:13:48 +00:00
|
|
|
endif
|
|
|
|
let s:debug_mode_already_setup = s:true
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:GetIniSetting(section, key)
|
|
|
|
if !filereadable(s:config_file)
|
|
|
|
return ''
|
|
|
|
endif
|
|
|
|
let lines = readfile(s:config_file)
|
|
|
|
let currentSection = ''
|
|
|
|
for line in lines
|
|
|
|
let line = s:StripWhitespace(line)
|
|
|
|
if matchstr(line, '^\[') != '' && matchstr(line, '\]$') != ''
|
|
|
|
let currentSection = substitute(line, '^\[\(.\{-}\)\]$', '\1', '')
|
|
|
|
else
|
|
|
|
if currentSection == a:section
|
|
|
|
let group = split(line, '=')
|
|
|
|
if len(group) == 2 && s:StripWhitespace(group[0]) == a:key
|
|
|
|
return s:StripWhitespace(group[1])
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
return ''
|
|
|
|
endfunction
|
|
|
|
|
2017-04-18 08:57:35 +00:00
|
|
|
function! s:SetIniSetting(section, key, val)
|
|
|
|
let output = []
|
|
|
|
let sectionFound = s:false
|
|
|
|
let keyFound = s:false
|
|
|
|
if filereadable(s:config_file)
|
|
|
|
let lines = readfile(s:config_file)
|
|
|
|
let currentSection = ''
|
|
|
|
for line in lines
|
2017-04-21 15:00:06 +00:00
|
|
|
let entry = s:StripWhitespace(line)
|
|
|
|
if matchstr(entry, '^\[') != '' && matchstr(entry, '\]$') != ''
|
2017-04-18 08:57:35 +00:00
|
|
|
if currentSection == a:section && !keyFound
|
|
|
|
let output = output + [join([a:key, a:val], '=')]
|
|
|
|
let keyFound = s:true
|
|
|
|
endif
|
2017-04-21 15:00:06 +00:00
|
|
|
let currentSection = substitute(entry, '^\[\(.\{-}\)\]$', '\1', '')
|
2017-04-18 08:57:35 +00:00
|
|
|
let output = output + [line]
|
|
|
|
if currentSection == a:section
|
|
|
|
let sectionFound = s:true
|
|
|
|
endif
|
|
|
|
else
|
|
|
|
if currentSection == a:section
|
2017-04-21 15:00:06 +00:00
|
|
|
let group = split(entry, '=')
|
2017-04-18 08:57:35 +00:00
|
|
|
if len(group) == 2 && s:StripWhitespace(group[0]) == a:key
|
|
|
|
let output = output + [join([a:key, a:val], '=')]
|
|
|
|
let keyFound = s:true
|
|
|
|
else
|
|
|
|
let output = output + [line]
|
|
|
|
endif
|
|
|
|
else
|
|
|
|
let output = output + [line]
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
endif
|
|
|
|
if !sectionFound
|
|
|
|
let output = output + [printf('[%s]', a:section), join([a:key, a:val], '=')]
|
|
|
|
else
|
|
|
|
if !keyFound
|
|
|
|
let output = output + [join([a:key, a:val], '=')]
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
call writefile(output, s:config_file)
|
|
|
|
endfunction
|
|
|
|
|
2013-07-02 09:24:04 +00:00
|
|
|
function! s:GetCurrentFile()
|
|
|
|
return expand("%:p")
|
|
|
|
endfunction
|
|
|
|
|
2018-04-04 06:00:15 +00:00
|
|
|
function! s:SanitizeArg(arg)
|
|
|
|
let sanitized = shellescape(a:arg)
|
2018-04-04 15:11:24 +00:00
|
|
|
let sanitized = substitute(sanitized, '!', '\\!', 'g')
|
2018-04-04 06:00:15 +00:00
|
|
|
return sanitized
|
2015-06-11 06:38:54 +00:00
|
|
|
endfunction
|
|
|
|
|
2017-04-22 09:09:01 +00:00
|
|
|
function! s:JsonEscape(str)
|
|
|
|
return substitute(a:str, '"', '\\"', 'g')
|
|
|
|
endfunction
|
|
|
|
|
2015-06-11 06:38:54 +00:00
|
|
|
function! s:JoinArgs(args)
|
|
|
|
let safeArgs = []
|
|
|
|
for arg in a:args
|
2018-04-04 06:00:15 +00:00
|
|
|
let safeArgs = safeArgs + [s:SanitizeArg(arg)]
|
2015-06-11 06:38:54 +00:00
|
|
|
endfor
|
|
|
|
return join(safeArgs, ' ')
|
|
|
|
endfunction
|
|
|
|
|
2017-04-13 07:19:37 +00:00
|
|
|
function! s:IsWindows()
|
|
|
|
if has('win32') || has('win64')
|
|
|
|
return s:true
|
|
|
|
endif
|
|
|
|
return s:false
|
|
|
|
endfunction
|
|
|
|
|
2017-04-24 07:43:59 +00:00
|
|
|
function! s:CurrentTimeStr()
|
|
|
|
if s:has_reltime
|
2017-04-24 07:55:32 +00:00
|
|
|
return split(reltimestr(reltime()))[0]
|
2017-04-24 07:43:59 +00:00
|
|
|
endif
|
2018-04-04 15:11:24 +00:00
|
|
|
return s:n2s(localtime())
|
2017-04-24 07:43:59 +00:00
|
|
|
endfunction
|
|
|
|
|
2017-04-22 09:09:01 +00:00
|
|
|
function! s:AppendHeartbeat(file, now, is_write, last)
|
2015-12-29 17:43:16 +00:00
|
|
|
let file = a:file
|
|
|
|
if file == ''
|
2017-04-22 20:39:07 +00:00
|
|
|
let file = a:last.file
|
2013-07-08 04:25:06 +00:00
|
|
|
endif
|
2015-12-29 17:43:16 +00:00
|
|
|
if file != ''
|
2017-04-22 09:09:01 +00:00
|
|
|
let heartbeat = {}
|
|
|
|
let heartbeat.entity = file
|
2017-04-24 07:44:41 +00:00
|
|
|
let heartbeat.time = s:CurrentTimeStr()
|
2017-04-22 09:09:01 +00:00
|
|
|
let heartbeat.is_write = a:is_write
|
2016-04-20 08:10:44 +00:00
|
|
|
if !empty(&syntax)
|
2017-04-22 09:09:01 +00:00
|
|
|
let heartbeat.language = &syntax
|
2016-04-19 10:52:30 +00:00
|
|
|
else
|
2016-04-20 08:10:44 +00:00
|
|
|
if !empty(&filetype)
|
2017-04-22 09:09:01 +00:00
|
|
|
let heartbeat.language = &filetype
|
2016-04-19 10:52:30 +00:00
|
|
|
endif
|
|
|
|
endif
|
2017-04-22 09:09:01 +00:00
|
|
|
let s:heartbeats_buffer = s:heartbeats_buffer + [heartbeat]
|
|
|
|
call s:SetLastHeartbeat(a:now, a:now, file)
|
2017-10-04 15:23:07 +00:00
|
|
|
|
|
|
|
if !s:buffering_heartbeats_enabled
|
2017-04-22 09:09:01 +00:00
|
|
|
call s:SendHeartbeats()
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
2018-04-04 05:36:37 +00:00
|
|
|
function! s:GetPythonBinary()
|
2017-04-22 09:09:01 +00:00
|
|
|
let python_bin = g:wakatime_PythonBinary
|
2018-04-04 05:30:20 +00:00
|
|
|
if !filereadable(python_bin)
|
|
|
|
let paths = ['python3']
|
|
|
|
if s:IsWindows()
|
2018-04-04 06:56:31 +00:00
|
|
|
let pyver = 39
|
2018-10-27 04:15:48 +00:00
|
|
|
while pyver >= 27
|
2018-04-04 05:30:20 +00:00
|
|
|
let paths = paths + [printf('/Python%d/pythonw', pyver), printf('/python%d/pythonw', pyver), printf('/Python%d/python', pyver), printf('/python%d/python', pyver)]
|
|
|
|
let pyver = pyver - 1
|
|
|
|
endwhile
|
|
|
|
else
|
|
|
|
let paths = paths + ['/usr/bin/python3', '/usr/local/bin/python3', '/usr/bin/python3.6', '/usr/local/bin/python3.6', '/usr/bin/python', '/usr/local/bin/python', '/usr/bin/python2', '/usr/local/bin/python2']
|
2017-04-22 09:09:01 +00:00
|
|
|
endif
|
2018-04-04 05:30:20 +00:00
|
|
|
let paths = paths + ['python']
|
|
|
|
let index = 0
|
|
|
|
let limit = len(paths)
|
|
|
|
while index < limit
|
|
|
|
if filereadable(paths[index])
|
|
|
|
let python_bin = paths[index]
|
|
|
|
let index = limit
|
|
|
|
endif
|
|
|
|
let index = index + 1
|
|
|
|
endwhile
|
2017-04-22 09:09:01 +00:00
|
|
|
endif
|
2018-04-04 05:30:20 +00:00
|
|
|
if s:IsWindows() && filereadable(printf('%sw', python_bin))
|
|
|
|
let python_bin = printf('%sw', python_bin)
|
|
|
|
endif
|
2018-04-04 05:36:37 +00:00
|
|
|
return python_bin
|
|
|
|
endfunction
|
|
|
|
|
2018-05-25 13:50:30 +00:00
|
|
|
function! s:GetCommandPrefix()
|
|
|
|
if exists("g:wakatime_OverrideCommandPrefix") && g:wakatime_OverrideCommandPrefix
|
|
|
|
let prefix = [g:wakatime_OverrideCommandPrefix]
|
|
|
|
else
|
|
|
|
let python_bin = s:GetPythonBinary()
|
|
|
|
let prefix = [python_bin, '-W', 'ignore', s:cli_location]
|
|
|
|
endif
|
|
|
|
return prefix
|
|
|
|
endfunction
|
|
|
|
|
2018-04-04 05:36:37 +00:00
|
|
|
function! s:SendHeartbeats()
|
|
|
|
let start_time = localtime()
|
|
|
|
let stdout = ''
|
|
|
|
|
|
|
|
if len(s:heartbeats_buffer) == 0
|
|
|
|
let s:last_sent = start_time
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let heartbeat = s:heartbeats_buffer[0]
|
|
|
|
let s:heartbeats_buffer = s:heartbeats_buffer[1:-1]
|
|
|
|
if len(s:heartbeats_buffer) > 0
|
|
|
|
let extra_heartbeats = s:GetHeartbeatsJson()
|
|
|
|
else
|
|
|
|
let extra_heartbeats = ''
|
|
|
|
endif
|
2018-04-04 05:30:20 +00:00
|
|
|
|
2018-05-25 13:50:30 +00:00
|
|
|
let cmd = s:GetCommandPrefix() + ['--entity', heartbeat.entity]
|
2017-04-22 09:09:01 +00:00
|
|
|
let cmd = cmd + ['--time', heartbeat.time]
|
2018-04-04 15:11:24 +00:00
|
|
|
let cmd = cmd + ['--plugin', printf('vim/%s vim-wakatime/%s', s:n2s(v:version), s:VERSION)]
|
2017-04-22 09:09:01 +00:00
|
|
|
if heartbeat.is_write
|
|
|
|
let cmd = cmd + ['--write']
|
|
|
|
endif
|
|
|
|
if has_key(heartbeat, 'language')
|
|
|
|
let cmd = cmd + ['--language', heartbeat.language]
|
|
|
|
endif
|
|
|
|
if extra_heartbeats != ''
|
|
|
|
let cmd = cmd + ['--extra-heartbeats']
|
|
|
|
endif
|
2017-10-04 15:23:07 +00:00
|
|
|
|
2017-10-19 02:08:56 +00:00
|
|
|
" overwrite shell
|
|
|
|
let [sh, shellcmdflag, shrd] = [&shell, &shellcmdflag, &shellredir]
|
|
|
|
if !s:IsWindows()
|
|
|
|
set shell=sh shellredir=>%s\ 2>&1
|
|
|
|
endif
|
|
|
|
|
2017-10-04 15:23:07 +00:00
|
|
|
if s:has_async
|
2018-04-05 07:19:33 +00:00
|
|
|
if s:IsWindows()
|
|
|
|
let job_cmd = [&shell, &shellcmdflag] + cmd
|
|
|
|
else
|
|
|
|
let job_cmd = [&shell, &shellcmdflag, s:JoinArgs(cmd)]
|
|
|
|
endif
|
|
|
|
let job = job_start(job_cmd, {
|
2017-10-04 15:23:07 +00:00
|
|
|
\ 'stoponexit': '',
|
2017-10-05 01:59:43 +00:00
|
|
|
\ 'callback': {channel, output -> s:AsyncHandler(output, cmd)}})
|
2017-10-04 15:23:07 +00:00
|
|
|
if extra_heartbeats != ''
|
|
|
|
let channel = job_getchannel(job)
|
|
|
|
call ch_sendraw(channel, extra_heartbeats . "\n")
|
2017-04-22 09:09:01 +00:00
|
|
|
endif
|
2017-10-05 01:59:43 +00:00
|
|
|
elseif s:nvim_async
|
2018-01-29 04:06:13 +00:00
|
|
|
let s:nvim_async_output = ['']
|
2017-10-05 01:59:43 +00:00
|
|
|
let job = jobstart([&shell, &shellcmdflag, s:JoinArgs(cmd)], {
|
|
|
|
\ 'detach': 1,
|
2018-01-29 04:06:13 +00:00
|
|
|
\ 'on_stdout': function('s:NeovimAsyncOutputHandler'),
|
|
|
|
\ 'on_stderr': function('s:NeovimAsyncOutputHandler'),
|
|
|
|
\ 'on_exit': function('s:NeovimAsyncExitHandler')})
|
2017-10-05 01:59:43 +00:00
|
|
|
if extra_heartbeats != ''
|
|
|
|
call jobsend(job, extra_heartbeats . "\n")
|
|
|
|
endif
|
2017-10-19 02:09:29 +00:00
|
|
|
elseif s:IsWindows()
|
|
|
|
if s:is_debug_on
|
|
|
|
if extra_heartbeats != ''
|
|
|
|
let stdout = system('(' . s:JoinArgs(cmd) . ')', extra_heartbeats)
|
|
|
|
else
|
|
|
|
let stdout = system('(' . s:JoinArgs(cmd) . ')')
|
|
|
|
endif
|
|
|
|
else
|
|
|
|
exec 'silent !start /b cmd /c "' . s:JoinArgs(cmd) . ' > nul 2> nul"'
|
|
|
|
endif
|
2017-04-22 09:09:01 +00:00
|
|
|
else
|
2017-10-19 02:09:29 +00:00
|
|
|
if s:is_debug_on
|
|
|
|
if extra_heartbeats != ''
|
|
|
|
let stdout = system(s:JoinArgs(cmd), extra_heartbeats)
|
2017-04-22 09:09:01 +00:00
|
|
|
else
|
2017-10-19 02:09:29 +00:00
|
|
|
let stdout = system(s:JoinArgs(cmd))
|
2017-04-22 09:09:01 +00:00
|
|
|
endif
|
|
|
|
else
|
2017-10-19 02:09:29 +00:00
|
|
|
if extra_heartbeats != ''
|
|
|
|
let stdout = system(s:JoinArgs(cmd) . ' &', extra_heartbeats)
|
2017-04-14 05:51:24 +00:00
|
|
|
else
|
2017-10-19 02:09:29 +00:00
|
|
|
let stdout = system(s:JoinArgs(cmd) . ' &')
|
2017-04-14 05:51:24 +00:00
|
|
|
endif
|
2014-05-27 05:39:37 +00:00
|
|
|
endif
|
2013-07-08 04:25:06 +00:00
|
|
|
endif
|
2017-10-04 15:23:07 +00:00
|
|
|
|
2017-10-19 02:08:56 +00:00
|
|
|
" restore shell
|
|
|
|
let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
|
|
|
|
|
2017-04-22 09:09:01 +00:00
|
|
|
let s:last_sent = localtime()
|
2017-09-04 17:13:34 +00:00
|
|
|
|
|
|
|
" need to repaint in case a key was pressed while sending
|
2017-10-05 01:59:43 +00:00
|
|
|
if !s:has_async && !s:nvim_async && s:redraw_setting != 'disabled'
|
2017-09-04 17:13:34 +00:00
|
|
|
if s:redraw_setting == 'auto'
|
|
|
|
if s:last_sent - start_time > 0
|
|
|
|
redraw!
|
|
|
|
endif
|
|
|
|
else
|
|
|
|
redraw!
|
|
|
|
endif
|
2017-04-25 14:56:04 +00:00
|
|
|
endif
|
2017-09-04 17:13:34 +00:00
|
|
|
|
2017-10-04 15:23:07 +00:00
|
|
|
if s:is_debug_on && stdout != ''
|
|
|
|
echoerr '[WakaTime] Heartbeat Command: ' . s:JoinArgs(cmd) . "\n[WakaTime] Error: " . stdout
|
2017-04-22 09:09:01 +00:00
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:GetHeartbeatsJson()
|
|
|
|
let arr = []
|
2017-04-27 03:02:22 +00:00
|
|
|
let loop_count = 1
|
2017-04-22 09:09:01 +00:00
|
|
|
for heartbeat in s:heartbeats_buffer
|
|
|
|
let heartbeat_str = '{"entity": "' . s:JsonEscape(heartbeat.entity) . '", '
|
2017-04-27 03:02:22 +00:00
|
|
|
let heartbeat_str = heartbeat_str . '"timestamp": ' . s:OrderTime(heartbeat.time, loop_count) . ', '
|
2017-04-22 09:09:01 +00:00
|
|
|
let heartbeat_str = heartbeat_str . '"is_write": '
|
|
|
|
if heartbeat.is_write
|
|
|
|
let heartbeat_str = heartbeat_str . 'true'
|
|
|
|
else
|
|
|
|
let heartbeat_str = heartbeat_str . 'false'
|
|
|
|
endif
|
|
|
|
if has_key(heartbeat, 'language')
|
|
|
|
let heartbeat_str = heartbeat_str . ', "language": "' . s:JsonEscape(heartbeat.language) . '"'
|
|
|
|
endif
|
|
|
|
let heartbeat_str = heartbeat_str . '}'
|
|
|
|
let arr = arr + [heartbeat_str]
|
2017-04-27 03:02:22 +00:00
|
|
|
let loop_count = loop_count + 1
|
2017-04-22 09:09:01 +00:00
|
|
|
endfor
|
|
|
|
let s:heartbeats_buffer = []
|
|
|
|
return '[' . join(arr, ',') . ']'
|
2013-07-08 04:25:06 +00:00
|
|
|
endfunction
|
2017-02-14 07:39:06 +00:00
|
|
|
|
2017-10-05 01:59:43 +00:00
|
|
|
function! s:AsyncHandler(output, cmd)
|
2017-10-04 15:23:07 +00:00
|
|
|
if s:is_debug_on && a:output != ''
|
|
|
|
echoerr '[WakaTime] Heartbeat Command: ' . s:JoinArgs(a:cmd) . "\n[WakaTime] Error: " . a:output
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
2018-01-29 04:06:13 +00:00
|
|
|
function! s:NeovimAsyncOutputHandler(job_id, output, event)
|
|
|
|
let s:nvim_async_output[-1] .= a:output[0]
|
|
|
|
call extend(s:nvim_async_output, a:output[1:])
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:NeovimAsyncExitHandler(job_id, exit_code, event)
|
|
|
|
let output = s:StripWhitespace(join(s:nvim_async_output, "\n"))
|
|
|
|
if a:exit_code == 104
|
|
|
|
let output .= 'Invalid API Key'
|
|
|
|
endif
|
|
|
|
if (s:is_debug_on || a:exit_code == 103 || a:exit_code == 104) && (a:exit_code != 0 || output != '')
|
|
|
|
echoerr printf('[WakaTime] Error %d: %s', a:exit_code, output)
|
2017-10-05 01:59:43 +00:00
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
2017-04-27 03:02:22 +00:00
|
|
|
function! s:OrderTime(time_str, loop_count)
|
|
|
|
" Add a milisecond to a:time.
|
|
|
|
" Time prevision doesn't matter, but order of heartbeats does.
|
2017-10-04 15:20:19 +00:00
|
|
|
if !(a:time_str =~ "\.")
|
2018-04-04 15:11:24 +00:00
|
|
|
let millisecond = s:n2s(a:loop_count)
|
2017-04-27 03:02:22 +00:00
|
|
|
while strlen(millisecond) < 6
|
|
|
|
let millisecond = '0' . millisecond
|
|
|
|
endwhile
|
|
|
|
return a:time_str . '.' . millisecond
|
|
|
|
endif
|
|
|
|
return a:time_str
|
|
|
|
endfunction
|
|
|
|
|
2015-05-01 23:08:02 +00:00
|
|
|
function! s:GetLastHeartbeat()
|
2017-04-22 20:40:54 +00:00
|
|
|
if !s:last_heartbeat.last_activity_at || localtime() - s:last_heartbeat.last_activity_at > s:local_cache_expire
|
2015-12-25 08:34:44 +00:00
|
|
|
if !filereadable(s:data_file)
|
2017-04-22 20:40:54 +00:00
|
|
|
return {'last_activity_at': 0, 'last_heartbeat_at': 0, 'file': ''}
|
2015-12-25 08:34:44 +00:00
|
|
|
endif
|
|
|
|
let last = readfile(s:data_file, '', 3)
|
|
|
|
if len(last) == 3
|
2017-04-22 20:39:07 +00:00
|
|
|
let s:last_heartbeat.last_heartbeat_at = last[1]
|
|
|
|
let s:last_heartbeat.file = last[2]
|
2015-12-25 08:34:44 +00:00
|
|
|
endif
|
2013-07-02 09:24:04 +00:00
|
|
|
endif
|
2015-12-25 08:34:44 +00:00
|
|
|
return s:last_heartbeat
|
|
|
|
endfunction
|
2017-02-14 07:39:06 +00:00
|
|
|
|
2017-04-22 20:40:54 +00:00
|
|
|
function! s:SetLastHeartbeatInMemory(last_activity_at, last_heartbeat_at, file)
|
|
|
|
let s:last_heartbeat = {'last_activity_at': a:last_activity_at, 'last_heartbeat_at': a:last_heartbeat_at, 'file': a:file}
|
2013-07-08 04:25:06 +00:00
|
|
|
endfunction
|
2017-02-14 07:39:06 +00:00
|
|
|
|
2018-04-04 15:11:24 +00:00
|
|
|
function! s:n2s(number)
|
|
|
|
return substitute(printf('%d', a:number), ',', '.', '')
|
|
|
|
endfunction
|
|
|
|
|
2017-04-22 20:40:54 +00:00
|
|
|
function! s:SetLastHeartbeat(last_activity_at, last_heartbeat_at, file)
|
|
|
|
call s:SetLastHeartbeatInMemory(a:last_activity_at, a:last_heartbeat_at, a:file)
|
2018-04-04 15:11:24 +00:00
|
|
|
call writefile([s:n2s(a:last_activity_at), s:n2s(a:last_heartbeat_at), a:file], s:data_file)
|
2013-07-02 09:24:04 +00:00
|
|
|
endfunction
|
|
|
|
|
2013-07-10 04:13:45 +00:00
|
|
|
function! s:EnoughTimePassed(now, last)
|
2017-04-22 20:39:07 +00:00
|
|
|
let prev = a:last.last_heartbeat_at
|
2015-02-13 03:19:40 +00:00
|
|
|
if a:now - prev > g:wakatime_HeartbeatFrequency * 60
|
2017-04-13 06:02:57 +00:00
|
|
|
return s:true
|
2013-07-08 04:25:06 +00:00
|
|
|
endif
|
2017-04-13 06:02:57 +00:00
|
|
|
return s:false
|
2013-07-02 09:24:04 +00:00
|
|
|
endfunction
|
2017-02-14 07:39:06 +00:00
|
|
|
|
2017-04-21 15:00:06 +00:00
|
|
|
function! s:PromptForApiKey()
|
|
|
|
let api_key = s:false
|
|
|
|
let api_key = s:GetIniSetting('settings', 'api_key')
|
|
|
|
if api_key == ''
|
|
|
|
let api_key = s:GetIniSetting('settings', 'apikey')
|
|
|
|
endif
|
|
|
|
|
2017-07-03 18:47:26 +00:00
|
|
|
let api_key = inputsecret("[WakaTime] Enter your wakatime.com api key: ", api_key)
|
2017-04-21 15:00:06 +00:00
|
|
|
call s:SetIniSetting('settings', 'api_key', api_key)
|
|
|
|
endfunction
|
|
|
|
|
2017-04-21 02:14:54 +00:00
|
|
|
function! s:EnableDebugMode()
|
|
|
|
call s:SetIniSetting('settings', 'debug', 'true')
|
2017-10-04 15:23:07 +00:00
|
|
|
let s:is_debug_on = s:true
|
2017-04-21 02:14:54 +00:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:DisableDebugMode()
|
|
|
|
call s:SetIniSetting('settings', 'debug', 'false')
|
2017-10-04 15:23:07 +00:00
|
|
|
let s:is_debug_on = s:false
|
2017-04-21 02:14:54 +00:00
|
|
|
endfunction
|
|
|
|
|
2017-04-25 14:56:04 +00:00
|
|
|
function! s:EnableScreenRedraw()
|
2017-09-04 17:13:34 +00:00
|
|
|
call s:SetIniSetting('settings', 'vi_redraw', 'enabled')
|
|
|
|
let s:redraw_setting = 'enabled'
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:EnableScreenRedrawAuto()
|
|
|
|
call s:SetIniSetting('settings', 'vi_redraw', 'auto')
|
|
|
|
let s:redraw_setting = 'auto'
|
2017-04-25 14:56:04 +00:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:DisableScreenRedraw()
|
2017-09-04 17:13:34 +00:00
|
|
|
call s:SetIniSetting('settings', 'vi_redraw', 'disabled')
|
|
|
|
let s:redraw_setting = 'disabled'
|
2017-04-25 14:56:04 +00:00
|
|
|
endfunction
|
|
|
|
|
2017-04-22 09:09:01 +00:00
|
|
|
function! s:InitAndHandleActivity(is_write)
|
2017-04-13 07:13:48 +00:00
|
|
|
call s:SetupDebugMode()
|
2015-01-20 04:36:23 +00:00
|
|
|
call s:SetupConfigFile()
|
2017-04-22 09:09:01 +00:00
|
|
|
call s:HandleActivity(a:is_write)
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:HandleActivity(is_write)
|
2015-12-29 17:43:16 +00:00
|
|
|
let file = s:GetCurrentFile()
|
2015-12-29 18:03:51 +00:00
|
|
|
if !empty(file) && file !~ "-MiniBufExplorer-" && file !~ "--NO NAME--" && file !~ "^term:"
|
2017-04-22 09:09:01 +00:00
|
|
|
let last = s:GetLastHeartbeat()
|
|
|
|
let now = localtime()
|
2017-04-22 20:08:49 +00:00
|
|
|
|
|
|
|
" Create a heartbeat when saving a file, when the current file
|
|
|
|
" changes, and when still editing the same file but enough time
|
|
|
|
" has passed since the last heartbeat.
|
2017-04-22 20:39:07 +00:00
|
|
|
if a:is_write || s:EnoughTimePassed(now, last) || file != last.file
|
2017-04-22 09:09:01 +00:00
|
|
|
call s:AppendHeartbeat(file, now, a:is_write, last)
|
2015-12-25 08:34:44 +00:00
|
|
|
else
|
2017-04-22 20:40:54 +00:00
|
|
|
if now - s:last_heartbeat.last_activity_at > s:local_cache_expire
|
2017-04-22 20:39:07 +00:00
|
|
|
call s:SetLastHeartbeatInMemory(now, last.last_heartbeat_at, last.file)
|
2015-12-25 08:34:44 +00:00
|
|
|
endif
|
2015-05-06 16:49:33 +00:00
|
|
|
endif
|
2017-04-22 20:08:49 +00:00
|
|
|
|
2017-10-04 15:23:07 +00:00
|
|
|
" When buffering heartbeats disabled, no need to re-check the
|
|
|
|
" heartbeats buffer.
|
|
|
|
if s:buffering_heartbeats_enabled
|
2017-04-22 20:08:49 +00:00
|
|
|
|
2017-10-04 15:23:07 +00:00
|
|
|
" Only send buffered heartbeats every s:send_buffer_seconds
|
|
|
|
if now - s:last_sent > s:send_buffer_seconds
|
2017-04-22 20:08:49 +00:00
|
|
|
call s:SendHeartbeats()
|
|
|
|
endif
|
2017-04-22 09:09:01 +00:00
|
|
|
endif
|
2013-07-02 09:24:04 +00:00
|
|
|
endif
|
2013-07-08 04:25:06 +00:00
|
|
|
endfunction
|
2013-07-02 09:24:04 +00:00
|
|
|
|
2013-06-25 07:57:15 +00:00
|
|
|
" }}}
|
|
|
|
|
2013-06-26 04:09:52 +00:00
|
|
|
|
2017-09-04 17:13:34 +00:00
|
|
|
call s:Init()
|
|
|
|
|
|
|
|
|
2013-06-25 07:57:15 +00:00
|
|
|
" Autocommand Events {{{
|
|
|
|
|
2013-07-02 09:24:04 +00:00
|
|
|
augroup Wakatime
|
2017-04-22 09:09:01 +00:00
|
|
|
autocmd BufEnter,VimEnter * call s:InitAndHandleActivity(s:false)
|
|
|
|
autocmd CursorMoved,CursorMovedI * call s:HandleActivity(s:false)
|
|
|
|
autocmd BufWritePost * call s:HandleActivity(s:true)
|
2017-04-30 11:54:19 +00:00
|
|
|
if exists('##QuitPre')
|
2017-04-30 15:59:45 +00:00
|
|
|
autocmd QuitPre * call s:SendHeartbeats()
|
2017-04-30 11:54:19 +00:00
|
|
|
endif
|
2013-07-02 09:24:04 +00:00
|
|
|
augroup END
|
2013-06-25 07:57:15 +00:00
|
|
|
|
|
|
|
" }}}
|
|
|
|
|
2013-06-26 04:09:52 +00:00
|
|
|
|
2017-04-21 02:14:54 +00:00
|
|
|
" Plugin Commands {{{
|
|
|
|
|
2017-04-21 15:00:06 +00:00
|
|
|
:command -nargs=0 WakaTimeApiKey call s:PromptForApiKey()
|
2017-04-21 02:14:54 +00:00
|
|
|
:command -nargs=0 WakaTimeDebugEnable call s:EnableDebugMode()
|
|
|
|
:command -nargs=0 WakaTimeDebugDisable call s:DisableDebugMode()
|
2017-04-25 14:56:04 +00:00
|
|
|
:command -nargs=0 WakaTimeScreenRedrawDisable call s:DisableScreenRedraw()
|
|
|
|
:command -nargs=0 WakaTimeScreenRedrawEnable call s:EnableScreenRedraw()
|
2017-09-04 17:13:34 +00:00
|
|
|
:command -nargs=0 WakaTimeScreenRedrawEnableAuto call s:EnableScreenRedrawAuto()
|
2017-04-21 02:14:54 +00:00
|
|
|
|
|
|
|
" }}}
|
|
|
|
|
|
|
|
|
2017-05-19 05:07:17 +00:00
|
|
|
" Restore wildignore option
|
|
|
|
if s:wildignore != ""
|
|
|
|
let &wildignore=s:wildignore
|
|
|
|
endif
|
|
|
|
|
2013-06-25 07:57:15 +00:00
|
|
|
" Restore cpoptions
|
|
|
|
let &cpo = s:old_cpo
|