Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 177 additions & 60 deletions autoload/ollama/logger.vim
Original file line number Diff line number Diff line change
Expand Up @@ -48,58 +48,11 @@ function! ollama#logger#BufReadCmd() abort
endtry
endfunction

let s:level_prefixes = ['', '[ERROR] ', '[WARN] ', '[INFO] ', '[DEBUG] ', '[DEBUG] ']

function! OllamaOpenLogBuffer() abort
" Check if the buffer already exists
let l:bufnr = bufnr('ollama:///log')

if l:bufnr <= 0
" Create a new buffer with that name and setup BufReadCmd autocommand
execute 'edit ollama:///log'
else
" Just switch to it
execute 'buffer' l:bufnr
endif

" Mark it as special (readonly, unlisted, etc.) if not already
setlocal buftype=nofile bufhidden=wipe nobuflisted nomodifiable
endfunction

command! OllamaLog call OllamaOpenLogBuffer()

" Fallback function to log to a Vim buffer if writing to file fails.
function! ollama#logger#ToBuffer(lines) abort
" Evaluate deferred log lines (functions)
call map(a:lines, { k, L -> type(L) == v:t_func ? call(L, []) : L })

" Extend log history
call extend(s:logs, a:lines)

" Enforce log history limit
let l:overflow = len(s:logs) - get(g:, 'ollama_log_history', 10000)
if l:overflow > 0
call remove(s:logs, 0, l:overflow - 1)
endif

" If log buffer is open and loaded, update it
let l:bufnr = bufnr('ollama:///log')
if l:bufnr > 0 && bufloaded(l:bufnr)
call setbufvar(l:bufnr, '&modifiable', 1)
call setbufline(l:bufnr, 1, s:logs)
call setbufvar(l:bufnr, '&modifiable', 0)

" Scroll other windows showing the log buffer
for l:winid in win_findbuf(l:bufnr)
if has('nvim') && l:winid != win_getid()
call nvim_win_set_cursor(l:winid, [len(s:logs), 0])
endif
endfor
endif
endfunction
let s:level_prefixes =
\ ['', '[ERROR] ', '[WARN] ', '[INFO] ', '[DEBUG] ', '[DEBUG] ']

" Raw logging function used by all log levels
function! ollama#logger#Raw(level, messages) abort
function! s:LogMessages(level, messages) abort
if a:level > g:ollama_debug
return
endif
Expand All @@ -116,6 +69,10 @@ function! ollama#logger#Raw(level, messages) abort
let l:lines += l:message
elseif type(l:message) == v:t_string
let l:lines += split(l:message, "\n", 1)
elseif type(l:message) == v:t_func
" Evaluate deferred log lines.
" TODO When does this happen?
call add(l:lines, call(l:message, []))
else
call add(l:lines, string(l:message))
endif
Expand All @@ -127,40 +84,200 @@ function! ollama#logger#Raw(level, messages) abort
\ .. get(l:lines, 0, '')

try
" write to file
if filewritable(g:ollama_logfile)
call writefile(l:lines, g:ollama_logfile, 'a')
return
endif

" of fall back to logging to a Vim buffer
call ollama#logger#ToBuffer(l:lines)
catch
" there is nothing we could do here
endtry

let l:overflow = s:UpdateInMemoryLog(l:lines)
call s:UpdateLogBuffer(l:overflow, len(l:lines))
endfunction

function! ollama#logger#Debug(...) abort
if empty(get(g:, 'ollama_debug'))
return
endif
call ollama#logger#Raw(4, a:000)
call s:LogMessages(4, a:000)
endfunction

function! ollama#logger#Info(...) abort
call ollama#logger#Raw(3, a:000)
call s:LogMessages(3, a:000)
endfunction

function! ollama#logger#Warn(...) abort
call ollama#logger#Raw(2, a:000)
call s:LogMessages(2, a:000)
endfunction

function! ollama#logger#Error(...) abort
call ollama#logger#Raw(1, a:000)
call s:LogMessages(1, a:000)
endfunction

function! ollama#logger#Bare(...) abort
call ollama#logger#Raw(0, a:000)
call s:LogMessages(0, a:000)
endfunction

function! s:SetLogLevel(level)
if a:level =~# '^[0-4]$'
let g:ollama_debug = a:level + 0
return v:true
endif

let l:level_names = ['bare', 'error', 'warn', 'info', 'debug']

if a:level ==? 'bare'
let g:ollama_debug = 0
return v:true
endif

if a:level ==? 'error'
let g:ollama_debug = 1
return v:true
endif

if a:level =~? '^warn\%[ing]$'
let g:ollama_debug = 2
return v:true
endif

if a:level ==? 'info'
let g:ollama_debug = 3
return v:true
endif

if a:level ==? 'debug'
let g:ollama_debug = 4
return v:true
endif

echoerr "ERROR: Unknown debug level name:" a:level
return v:false
endfunction

function! s:UpdateInMemoryLog(lines)
let overflow =
\ len(s:logs) + len(a:lines) - get(g:, 'ollama_log_history', 10000)
if overflow > 0
call remove(s:logs, 0, overflow - 1)
else
let overflow = 0
endif

call extend(s:logs, a:lines)

return overflow
endfunction

function! s:UpdateLogBuffer(overflow, new_lines)
let bufnr = bufnr('ollama:///log')
if bufnr == -1 || !bufloaded(bufnr)
return
endif

" We are going to preserve cursor position in all windows that show the log
" buffer. As a special case, if the cursor is on the last line, it will keep
" moving along with the log, staying in the last line.
"
" We do this for all windows, regardless of if they are in the current tab or
" not.
let current_winid = win_getid()
let new_cursor_positions = []
for winid in win_findbuf(bufnr)
let [_, lnum, _, _, _] = getcurpos(winid)
" Was cursor was on the last line before a:new_lines were added.
if lnum == len(s:logs) + a:overflow - a:new_lines
let lnum = lnum + a:new_lines - a:overflow
else
let lnum = max([1, lnum - a:overflow])
endif
call add(new_cursor_positions, (winid, lnum))
endfor

call setbufvar(bufnr, '&modifiable', 1)
" Optimize the buffer update a bit.
" Rewriting the whole buffer content is slow. But even writing just the
" difference can also be slow, if it happens on each key stroke.
" Further optimization would require an async update? Which is probably too
" much work.
if a:overflow > 0
call deletebufline(bufnr, 1, a:overflow)
endif
if a:new_lines > 0
call appendbufline(bufnr, '$', s:logs[-a:new_lines:])
endif
call setbufvar(bufnr, '&modifiable', 0)
call setbufvar(bufnr, '&modified', 0)

for [winid, lnum] in new_cursor_positions
call win_gotoid(winid)
call winrestview({ 'lnum': lnum })
endfor

call win_gotoid(current_winid)
endfunction

function! ollama#logger#ShowLogBuffer(mods, ...) abort
if len(a:0) > 1
echoerr "ERROR: OllamaDebug accepts a maximum of 1 optional argument"
return
endif

let l:bufnr = bufnr('ollama:///log')

" Set the log level, or process the "stop" command.
if len(a:0) == 1
let l:level = a:1
if l:level =~? 'stop'
if l:bufnr != -1
execute l:bufnr .. 'bwipe'
endif
let g:ollama_debug = 1
return
endif

if !s:SetLogLevel(l:level)
return
endif
endif

" If the buffer is already visible in the current tab, then we are done.
if l:bufnr != -1 && bufloaded(l:bufnr)
for l:winnr in range(1, winnr('$'))
if winbufnr(l:winnr) == l:bufnr
return
endif
endfor
endif

" Create new window and show the log buffer in there.

let current_win = winnr()
silent execute a:mods 'new'

if l:bufnr == -1 || !bufloaded(l:bufnr)
setlocal buftype=nofile
setlocal bufhidden=hide
setlocal nobuflisted
setlocal noswapfile
silent 0file
silent keepalt file 'ollama:///log'
set filetype=log
let l:bufnr = bufnr('ollama:///log')
call setbufline(l:bufnr, 1, s:logs)
setlocal nomodified nomodifiable

" Move cursor to the last line, which would cause the log to keep showing
" the last line.
call setpos('.', [0, len(s:logs), 1, 0, 1])
else
" Show the existing buffer.
execute 'buffer' l:bufnr
endif

" Keep the focused window as is, as it is unlikely that the user wants to
" focus the log?
execute current_win .. 'wincmd w'
endfunction

if !exists('s:log_open')
Expand Down
36 changes: 36 additions & 0 deletions doc/vim-ollama.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,42 @@ The vim-ollama plugin provides the following commands:
>
:OllamaPull qwen2.5-coder:1.5b
<
:OllamaDebug
- Description: Opens a buffer that will show the last
`g:ollama_log_history` lines of the plugin log.

Optionally accepts a log level, which is also specified in the
`g:ollama_debug` variable.

The log level can be either a number between 0 and 4 inclusive, or it can
be a name of the log level. Log level names are, case insensitive:
"bare", "error", "warn" (or "warning"), "info", "debug". Names match
levels 0 to 4, respectively.

The command will create a buffer with no actual file backing it, called
`ollama:///log`, unless it already exist. As long as this buffer will
exist, its content will contain the last `g:ollama_log_history` lines in
the plugin log. If the cursor is on the last line in a window showing
this buffer, the cusor position will be automatically updated to always
show the last line.

If you pass "stop" as the log level, then the log buffer is destroyed and
the log level is set to "error".

You can also just `:bw` the debug buffer when you no longer need it.

Notice that the log messages are preserved regardless of if the log buffer
is visible or not. But only messages that match the currently set log
level are stored.

- Usage:
>
:OllamaDebug [log-level | "stop"]
<
- Example:
>
:vert OllamaDebug debug
<

==============================================================================
5. Maps *vim-ollama-maps*
Expand Down
2 changes: 2 additions & 0 deletions plugin/ollama.vim
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ command! OllamaChat call ollama#review#Chat()
command! -nargs=1 -complete=customlist,ollama#CommandComplete Ollama call ollama#Command(<f-args>)
command! -nargs=1 OllamaPull call ollama#setup#PullModel(g:ollama_host, <f-args>)

command! -nargs=? OllamaDebug call ollama#logger#ShowLogBuffer(<q-mods>, <f-args>)

" Define new signs for diffs
sign define NewLine text=+ texthl=DiffAdd
sign define ChangedLine text=~ texthl=DiffChange
Expand Down