Skip to content
Enno edited this page May 30, 2024 · 12 revisions

How to automatically break lines in the Chat

More details in issue #27

let g:vim_ai_chat = {
\  "ui": {
\    "paste_mode": 0,
\  },
\}
autocmd FileType aichat setlocal textwidth=80

How to reuse chat across vim sessions

In case you would like to restore the same conversation from the file, here's how to configure it:

let g:vim_ai_chat = {
\  "ui": {
\    "open_chat_command": "below new /tmp/last_conversation.aichat",
\  },
\}

You can store conversations in .aichat files and open them later if you need to work with multiple chat sessions, having your own prompt templates, etc.

How to navigate a chat

Adding the following code lets you jump back and forth between the prompts in the AI Chat buffer by hitting [[ respectively ]] and select a question/reply by i/ac:

function! s:AIChatSetupJump2Section()
  " See also https://github.com/tpope/vim-markdown/commit/191438f3582a532b72c9f8a1d6c0477050ccddef
  nnoremap <buffer><silent> ]] :<c-u>call <SID>AIChatJump2Section( v:count1, '' , 0)<CR>
  nnoremap <buffer><silent> [[ :<c-u>call <SID>AIChatJump2Section( v:count1, 'b', 0)<CR>
  xnoremap <buffer><silent> ]] :<c-u>call <SID>AIChatJump2Section( v:count1, '' , 1)<CR>
  xnoremap <buffer><silent> [[ :<c-u>call <SID>AIChatJump2Section( v:count1, 'b', 1)<CR>
  onoremap <buffer><silent> ]] :<c-u>normal ]]<CR>
  onoremap <buffer><silent> [[ :<c-u>normal [[<CR>
  let b:undo_ftplugin .= '|sil! nunmap <buffer> [[|sil! nunmap <buffer> ]]|sil! xunmap <buffer> [[|sil! xunmap <buffer> ]]'
  " From https://github.com/plasticboy/vim-markdown/issues/282#issuecomment-725909968
  xnoremap <buffer><silent> ic :<C-U>call <SID>AIChatBlockTextObj('i')<CR>
  onoremap <buffer><silent> ic :<C-U>call <SID>AIChatBlockTextObj('i')<CR>

  xnoremap <buffer><silent> ac :<C-U>call <SID>AIChatBlockTextObj('a')<CR>
  onoremap <buffer><silent> ac :<C-U>call <SID>AIChatBlockTextObj('a')<CR>
  let b:undo_ftplugin .= '|sil! ounmap <buffer> ic|sil! ounmap <buffer> ac|sil! xunmap <buffer> ic|sil! xunmap <buffer> ac'
endfunction

function s:AIChatJump2Section( cnt, dir, vis ) abort
  if a:vis
    normal! gv
  endif

  let i = 0
  let pattern = '\v^\>{3,3} user$|^\<{3,3} assistant$'
  let flags = 'W' . a:dir
  while i < a:cnt && search( pattern, flags ) > 0
    let i = i+1
  endwhile
endfunction

function s:AIChatBlockTextObj(type) abort
  " the parameter type specify whether it is inner text objects or arround
  " text objects.
  let start_row = searchpos('\v^\>{3,3} user$|^\<{3,3} assistant$', 'bn')[0]
  let end_row = searchpos('\v^\>{3,3} user$|^\<{3,3} assistant$', 'n')[0]

  let buf_num = bufnr('%')
  if a:type ==# 'i'
    let start_row += 1
    let end_row -= 1
  endif
  " echo a:type start_row end_row

  call setpos("'<", [buf_num, start_row, 1, 0])
  call setpos("'>", [buf_num, end_row, 1, 0])
  execute 'normal! `<V`>'
endfunction

augroup VimAI
  autocmd!
  autocmd FileType aichat call s:AIChatSetupJump2Section()
augroup end

Adding the following code in ~/.vim/after/ftplugin/aichat.vim (where ~/.vim is %USERPROFILE/vimfiles on Microsoft Windowslets you switch between opening and closing markdown andaichat tags (>>>and<<<) in an AIChat window provided that matchitis enabled (by addingpackadd! matchitto yourvimrc; see :help matchit-install`):

" add markdown pairs and >>> / <<<
let b:match_words =
      \       '\%(^\|[ (/]\)\zs"' . ':' . '"\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|[ (/]\)\zs''' . ':' . '''\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \
      \ ',' . '\%(^\|[ (/]\)\zs\*[^* ]' . ':' . '[^* ]\zs\*\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|[ (/]\)\zs\*\*[^* ]' . ':' . '[^* ]\zs\*\*\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|[ (/]\)\zs\*\*\*' . ':' . '\*\*\*\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|[ (/]\)\zs_[^_ ]' . ':' . '[^_ ]\zs_\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|[ (/]\)\zs__[^_ ]' . ':' . '[^_ ]\zs__\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|[ (/]\)\zs___' . ':' . '___\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|[ (/]\)\zs`[^` ]' . ':' . '[^` ]\zs`\ze\%($\|[ )/.\,;\:?!\-]\)' .
      \ ',' . '\%(^\|\s\)\zs```\w\+$' . ':' . '\%(^\|\s\)\zs```\ze$' .
      \
      \ ',' . '^>>>\s' . ':' . '^<<<\ze\s' .
      \
      \ ',' . get(b:, 'match_words', &l:matchpairs)

How to fold questions and their answers

Folding makes navigation even easier. This code inside ~/.vim/after/ftplugin/aichat.vim (where ~/.vim is %USERPROFILE/vimfiles on Microsoft Windows` lets you fold your questions in an AIChat window:

if has("folding")
  setlocal foldexpr=AIChatFold()
  setlocal foldmethod=expr
  setlocal foldtext=AIChatFoldText()
  if exists('b:undo_ftplugin')
    let b:undo_ftplugin .= "|setl foldexpr< foldmethod< foldtext<"
  else
    let b:undo_ftplugin  = "|setl foldexpr< foldmethod< foldtext<"
  endif
endif

" The rest of the file needs to be :sourced only once per session.
if exists('s:loaded_functions') || &cp
  finish
endif
let s:loaded_functions = 1

function! AIChatFold() abort
  return getline(v:lnum) =~# '^>>> user$' ? '>1' : '='
endfunction

function! AIChatFoldText() abort
  let nextline = getline(v:foldstart + 2)
  let title = nextline
  let foldsize = (v:foldend - v:foldstart + 1)
  let linecount = '['.foldsize.' lines]'
  return title.' '.linecount
endfunction

How to let the AI explain selected terms

setlocal keywordprg=:AIChat\ Please\ explain\ this\ term\ briefly: