Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ To use an AI command, type the command followed by an instruction prompt. You ca

:AI complete text
:AIEdit edit text
:AIChat continue or open new chat
:AIChat toggle chat window (tab-local)
:AIStopChat stop the generation of the AI response for the AIChat
:AIImage generate image

Expand Down Expand Up @@ -208,13 +208,14 @@ In the documentation below, `<selection>` denotes a visual selection or any oth

### `:AIChat`

`:AIChat` - continue or start a new conversation.
`:AIChat` - open chat window on current tab, jump to it if already open, reopen it if hidden.
Each tab keeps its own chat buffer.

`<selection>? :AIChat {instruction}?` - start a new conversation given the selection, the instruction or both
`<selection>? :AIChat {instruction}?` - open or focus the tab-local chat and optionally initialize it with selection/instruction

`<selection>? :AIChat /{role} {instruction}?` - use role to complete
`<selection>? :AIChat /{role} {instruction}?` - open/focus chat and apply role to chat initialization

When the AI finishes answering, you can continue the conversation by entering insert mode, adding your prompt, and then using the command `:AIChat` once again.
Use `:AIChat` inside an `aichat` buffer to continue the conversation.

[Pre-defined](./roles-default.ini) chat roles: `/right`, `/below`, `/tab`

Expand Down
161 changes: 77 additions & 84 deletions autoload/vim_ai.vim
Original file line number Diff line number Diff line change
Expand Up @@ -21,65 +21,46 @@ function! s:ImportPythonModules()
endfor
endfunction

function! s:StartsWith(longer, shorter) abort
return a:longer[0:len(a:shorter)-1] ==# a:shorter
function! vim_ai#ImportPythonModules() abort
call s:ImportPythonModules()
endfunction

function! s:GetLastScratchBufferName()
let l:all_buffer_names = map(map(filter(copy(getbufinfo()), 'v:val.listed'), 'v:val.bufnr'), 'bufname(v:val)')
let l:buffer_name = -1
for l:name in l:all_buffer_names
if s:StartsWith(l:name, s:scratch_buffer_name)
let l:buffer_name = l:name
endif
endfor
return l:buffer_name
function! s:GetTabChatBuffer(tabnr) abort
let l:chat_bufnr = gettabvar(a:tabnr, 'vim_ai_chat_bufnr', -1)
if l:chat_bufnr == -1 || !bufexists(l:chat_bufnr)
return -1
endif
if getbufvar(l:chat_bufnr, '&filetype') !=# 'aichat'
return -1
endif
return l:chat_bufnr
endfunction

" Configures ai-chat scratch window.
" - scratch_buffer_keep_open = 0
" - opens new ai-chat every time
" - excludes buffer from buffer list
" - scratch_buffer_keep_open = 1
" - opens last ai-chat buffer (unless force_new = 1)
" - keeps hidden ai-chat buffer for later reuse in the same tab
" - keeps the buffer in the buffer list
function! s:OpenChatWindow(open_conf, force_new) abort
" open new buffer that will be used as a chat
let l:open_cmd = has_key(g:vim_ai_open_chat_presets, a:open_conf)
\ ? g:vim_ai_open_chat_presets[a:open_conf]
\ : a:open_conf
function! s:OpenChatWindow(open_conf) abort
let l:open_cmd = get(g:vim_ai_open_chat_presets, a:open_conf, a:open_conf)
execute l:open_cmd

" reuse chat in keep-open mode
let l:keep_open = g:vim_ai_chat['ui']['scratch_buffer_keep_open'] == '1'
let l:last_scratch_buffer_name = s:GetLastScratchBufferName()
if l:keep_open && bufexists(l:last_scratch_buffer_name) && !a:force_new
let l:current_buffer = bufnr('%')
" reuse chat buffer
execute "buffer " . l:last_scratch_buffer_name
" close new buffer that was created by l:open_cmd
execute "bd " . l:current_buffer
return
endif
let l:keep_open = g:vim_ai_chat['ui']['scratch_buffer_keep_open'] ==# '1'

setlocal buftype=nofile
setlocal noswapfile
setlocal ft=aichat
if l:keep_open
setlocal bufhidden=hide
else
setlocal bufhidden=wipe
endif
if bufexists(s:scratch_buffer_name)
" spawn another window if chat already exist
let l:index = 2
while bufexists(s:scratch_buffer_name . " " . l:index)
let l:index += 1
endwhile
execute "file " . s:scratch_buffer_name . " " . l:index
else
execute "file " . s:scratch_buffer_name
endif
setlocal filetype=aichat
execute 'setlocal bufhidden=' . (l:keep_open ? 'hide' : 'wipe')

let l:buffer_name = s:scratch_buffer_name
let l:index = 2
while bufexists(l:buffer_name)
let l:buffer_name = s:scratch_buffer_name . ' ' . l:index
let l:index += 1
endwhile
execute 'file ' . fnameescape(l:buffer_name)
endfunction

let s:is_handling_paste_mode = 0
Expand Down Expand Up @@ -241,43 +222,59 @@ function! vim_ai#AIImageRun(uses_range, config, ...) range abort
py3 run_ai_image(unwrap('l:context'))
endfunction

function! s:ReuseOrCreateChatWindow(config)
let l:open_conf = a:config['ui']['open_chat_command']
function! s:ReuseOrCreateChatWindow(config) abort
let l:tabnr = tabpagenr()
let l:force_new = a:config['ui']['force_new_chat'] ==# '1'

if a:config['ui']['force_new_chat'] == '1'
call s:OpenChatWindow(l:open_conf, 1)
if &filetype ==# 'aichat'
call settabvar(l:tabnr, 'vim_ai_chat_bufnr', bufnr('%'))
return
endif

if &filetype != 'aichat'
" reuse chat in active window or tab
let l:chat_win_ids = win_findbuf(bufnr(s:scratch_buffer_name))
if !empty(l:chat_win_ids)
call win_gotoid(l:chat_win_ids[0])
return
endif
let l:chat_bufnr = s:GetTabChatBuffer(l:tabnr)
let l:chat_win_id = -1
let l:is_visible_outside_tab = 0

if l:chat_bufnr != -1
for l:win_id in win_findbuf(l:chat_bufnr)
let l:win_tabnr = win_id2tabwin(l:win_id)[0]
if l:win_tabnr == l:tabnr
let l:chat_win_id = l:win_id
else
let l:is_visible_outside_tab = 1
endif
endfor

" reuse .aichat file on the same tab
let buffer_list_tab = tabpagebuflist(tabpagenr())
let buffer_list_tab = filter(buffer_list_tab, 'getbufvar(v:val, "&filetype") ==# "aichat"')
if len(buffer_list_tab) > 0
call win_gotoid(win_findbuf(buffer_list_tab[0])[0])
return
if l:is_visible_outside_tab
let l:chat_bufnr = -1
let l:chat_win_id = -1
endif
endif

" reuse any .aichat buffer in the session
let buffer_list = []
for i in range(tabpagenr('$'))
call extend(buffer_list, tabpagebuflist(i + 1))
endfor
let buffer_list = filter(buffer_list, 'getbufvar(v:val, "&filetype") ==# "aichat"')
if len(buffer_list) > 0
call win_gotoid(win_findbuf(buffer_list[0])[0])
return
endif
if !l:force_new && l:chat_win_id != -1
call win_gotoid(l:chat_win_id)
call settabvar(tabpagenr(), 'vim_ai_chat_bufnr', bufnr('%'))
return
endif

call s:OpenChatWindow(a:config['ui']['open_chat_command'])
let l:new_tabnr = tabpagenr()
let l:new_chat_bufnr = bufnr('%')

" open new chat window if no active buffer found
call s:OpenChatWindow(l:open_conf, 0)
if !l:force_new && l:chat_bufnr != -1
execute 'buffer ' . l:chat_bufnr
execute 'bwipeout ' . l:new_chat_bufnr
call settabvar(tabpagenr(), 'vim_ai_chat_bufnr', bufnr('%'))
return
endif

call settabvar(l:new_tabnr, 'vim_ai_chat_bufnr', l:new_chat_bufnr)

if l:force_new
\ && l:new_tabnr == l:tabnr
\ && l:chat_bufnr != -1
\ && l:chat_bufnr != l:new_chat_bufnr
execute 'bwipeout ' . l:chat_bufnr
endif
endfunction

Expand Down Expand Up @@ -322,12 +319,12 @@ function! vim_ai#AIChatRun(uses_range, config, ...) range abort
let l:started_from_chat = &filetype == 'aichat'

let l:config_input = {
\ "config_default": g:vim_ai_chat,
\ "config_extension": a:config,
\ "user_instruction": l:instruction,
\ "user_selection": l:selection,
\ "is_selection": l:is_selection,
\ "command_type": 'chat',
\ 'config_default': g:vim_ai_chat,
\ 'config_extension': a:config,
\ 'user_instruction': l:instruction,
\ 'user_selection': l:selection,
\ 'is_selection': l:is_selection,
\ 'command_type': 'chat',
\}
let l:context = py3eval("make_ai_context(unwrap('l:config_input'))")
let l:config = l:context['config']
Expand All @@ -346,15 +343,12 @@ function! vim_ai#AIChatRun(uses_range, config, ...) range abort
return
endif

let s:last_command = "chat"
let s:last_command = 'chat'
let s:last_config = a:config

if py3eval("run_ai_chat(unwrap('l:context'))")
if g:vim_ai_async_chat == 1

call setbufvar(l:bufnr, 'vim_ai_chat_start_last_line', line('$'))
" if user switches to a different buffer, setup autocommand that
" will clean undo history after returning back
augroup AichatUndo
au!
autocmd BufEnter <buffer> call s:AIChatUndoCleanup()
Expand All @@ -380,7 +374,6 @@ function! vim_ai#AIChatStopRun() abort
call s:AIChatUndoCleanup()
endfunction


" Function called in a timer that check if there are new lines from AI and
" appned them in a buffer. It ends when AI thread is finished (or when
" stopped).
Expand Down
9 changes: 6 additions & 3 deletions doc/vim-ai.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ https://github.com/madox2/vim-ai
:AI {prompt} complete the prompt
<selection> :AI complete the selection
<selection> :AI {instruction} complete the selection using the instruction
In aichat buffers: :AI continue chat completion

Options: >
let s:initial_complete_prompt =<< trim END
Expand Down Expand Up @@ -117,9 +118,11 @@ https://platform.openai.com/docs/api-reference/completions

*:AIChat*

:AIChat continue or start a new conversation.
<selection>? :AIChat {instruction}? start a new conversation given the selection,
the instruction or both
:AIChat open/focus tab-local chat window
Each tab keeps its own chat buffer.
In aichat buffers this continues the chat.
<selection>? :AIChat {instruction}? open/focus tab-local chat and optionally
initialize it using selection/instruction

Options: >
let s:initial_chat_prompt =<< trim END
Expand Down
2 changes: 2 additions & 0 deletions ftplugin/aichat.vim
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,5 @@ augroup Aichat
autocmd InsertEnter,InsertLeave <buffer> call s:MarkdownRefreshSyntax(0)
autocmd CursorHold,CursorHoldI <buffer> call s:MarkdownRefreshSyntax(0)
augroup END

command! -buffer -range -nargs=? -complete=customlist,vim_ai#RoleCompletionChat AI <line1>,<line2>call vim_ai#AIChatRun(<range>, {}, <q-args>)
Loading