Skip to content

Commit

Permalink
Close #31 - Feat: Add neural completion buffer
Browse files Browse the repository at this point in the history
Add a buffer that invokes a neural completion for the entire buffer
contents.

* Tech: Split neural prompt and run and add line options

* Config: Add initialisation settings for neural buffer

* Config: Add completion trigger plug mapping for neural buffer

* Fix: prompt ui escape keys

* Test: Add filetype in test vimrc for neural buffer tests
  • Loading branch information
Angelchev committed Dec 28, 2023
1 parent 9f37d8c commit 0cbcb93
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 78 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
/env
__pycache__
tags
# pyenv
.python-version
14 changes: 12 additions & 2 deletions autoload/neural.vim
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,18 @@ function! neural#Prompt(prompt) abort
return
endif

call neural#Run(a:prompt, {})
endfunction

function! neural#Run(prompt, options) abort
let l:buffer = bufnr('')
let l:moving_line = getpos('.')[1]

if has_key(a:options, 'line')
let l:moving_line = a:options.line
else
let l:moving_line = getpos('.')[1]
endif

let s:request_line = l:moving_line

if len(getline(l:moving_line)) == 0
Expand Down Expand Up @@ -322,7 +332,7 @@ function! neural#Prompt(prompt) abort
let s:current_job = l:job_id

" Tell the user something is happening, if enabled.
if g:neural.ui.echo_enabled
if g:neural.ui.echo_enabled && get(a:options, 'echo', 1)
" Echo with a 0 millisecond timer to avoid 'Press Enter to Continue'
let s:initial_timer = timer_start(0, {-> s:InitiallyInformUser(l:job_id)})

Expand Down
83 changes: 83 additions & 0 deletions autoload/neural/buffer.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
" Author: Anexon <[email protected]>
" Description: A Neural Scratch Buffer acts as a playground for interacting with
" Neural sources directly, sending all content of the buffer to the source.

scriptencoding utf-8

call neural#config#Load()

function! s:GetOptions(options_dict_string) abort
call neural#config#Load()

" TODO: Set buffer name based on source.
let l:options = {
\ 'name': 'Neural Buffer',
\ 'create_mode': g:neural.buffer.create_mode,
\ 'wrap': g:neural.buffer.wrap,
\}

" Override default options for the buffer instance.
if !empty(a:options_dict_string)
let l:options_dict = eval(a:options_dict_string)

if has_key(l:options_dict, 'name')
let l:options.name = l:options_dict.name
endif

if has_key(l:options_dict, 'create_mode')
let l:options.create_mode = l:options_dict.create_mode
endif

if has_key(l:options_dict, 'wrap')
let l:options.wrap = l:options_dict.wrap
endif
endif

return l:options
endfunction

function! neural#buffer#CreateBuffer(options) abort
let l:buffer_options = s:GetOptions(a:options)
" echo l:buffer_options.name
" echo bufnr(l:buffer_options.name)

" TODO: Add auto incrementing buffer names instead of switching.
if bufexists(l:buffer_options.name)
execute 'buffer' bufnr(l:buffer_options.name)
else
if l:buffer_options.create_mode is# 'vertical'
vertical new
elseif l:buffer_options.create_mode is# 'horizontal'
new
else
call neural#OutputErrorMessage('Invalid create mode for Neural Buffer. Must be horizontal or vertical.')
endif

if l:buffer_options.wrap
setlocal wrap linebreak
else
setlocal nowrap nolinebreak
endif

execute 'file ' . escape(l:buffer_options.name, ' ')
setlocal filetype=neuralbuf
setlocal buftype=nofile
setlocal bufhidden=hide
setlocal noswapfile
endif

" Switch into insert mode when entering the buffer
startinsert
endfunction

function! neural#buffer#RunBuffer() abort
let l:buffer_contents = join(getline(1, '$'), "\n")
let l:options = {
\ 'line': line('$'),
\ 'echo': 0,
\}

if &filetype is# 'neuralbuf'
call neural#Run(l:buffer_contents, l:options)
endif
endfunction
5 changes: 5 additions & 0 deletions autoload/neural/config.vim
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ let s:defaults = {
\ 'animated_sign_enabled': v:true,
\ 'echo_enabled': v:true,
\ },
\ 'buffer': {
\ 'completion_key': '\<C-CR>',
\ 'create_mode': 'vertical',
\ 'wrap': v:true,
\ },
\ 'source': {
\ 'openai': {
\ 'api_key': '',
Expand Down
18 changes: 18 additions & 0 deletions ftplugin/neuralbuf.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
" Author: Anexon <[email protected]>
" Description: Neural Buffer for interacting with neural sources directly.

call neural#config#Load()

command! -buffer -nargs=0 NeuralRun :call neural#buffer#RunBuffer()

nnoremap <buffer> <Plug>(neural_completion) :NeuralRun<Return>
" Keybindings of Neural Buffer
if exists('*keytrans') && exists('g:neural.buffer.completion_key')
execute 'nnoremap ' . keytrans(g:neural.buffer.completion_key) . ' <Plug>(neural_completion)'
execute 'inoremap ' . keytrans(g:neural.buffer.completion_key) . ' <Esc><Plug>(neural_completion)'
else
nnoremap <C-CR> <Plug>(neural_completion)
inoremap <C-CR> <Esc><Plug>(neural_completion)
endif

2 changes: 0 additions & 2 deletions lua/neural.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
local next = next

-- External dependencies
local UI = {}
local AnimatedSign = {}
Expand Down
130 changes: 57 additions & 73 deletions lua/neural/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,81 +9,65 @@ local UI = {}
--- @param title string The title of the prompt.
--- @param on_submit function The function to call when the user submits the prompt.
function UI.prompt(title, on_submit)
-- TODO: Make escape keys configurable.
local exit_keys = {
{
'n',
'q',
function(_)
vim.api.nvim_command(':q')
end,
{ noremap = true },
},
{
'n',
'<ESC>',
function(_)
vim.api.nvim_command(':q')
end,
{ noremap = true },
},
{
'i',
'<ESC>',
function(_)
vim.api.nvim_command(':q')
end,
{ noremap = true },
},
{
'i',
'<C-c>',
function(_)
vim.api.nvim_command(':q')
end,
{ noremap = true },
},
}
-- TODO: Make escape keys configurable.
local exit_keys = {
{'n', 'q',
function(_)
vim.api.nvim_command(':q')
end, {noremap = true},
},
{'n', '<ESC>',
function(_)
vim.api.nvim_command(':q')
end, {noremap = true},
},
{'i', '<ESC>',
function(_)
vim.api.nvim_command(':q')
end, {noremap = true},
},
{'i', '<C-c>',
function(_)
vim.api.nvim_command(':q')
end, {noremap = true},
},
}

-- TODO: Make prompt more configurable.
local input = Input({
position = { row = '85.2%', col = '50%' },
size = {
width = '51.8%',
height = '20%',
},
relative = 'editor',
border = {
highlight = 'NeuralPromptBorder',
style = 'rounded',
text = {
top = title,
top_align = 'center',
},
},
win_options = {
winblend = 10,
winhighlight = 'Normal:Normal',
},
}, {
prompt = vim.g.neural.ui.prompt_icon .. ' ',
default_value = '',
on_close = function() end,
on_submit = function(value)
on_submit(value)
end,
})
input:mount()
-- Cleanup on lost focus & window close
input:on({ Event.BufLeave, Event.BufWinLeave }, function()
vim.schedule(function()
input:unmount()
-- TODO: Make prompt more configurable.
local input = Input({
position = {row = '85.2%', col = '50%'},
size = {
width = '51.8%',
height = '20%',
},
relative = 'editor',
border = {
highlight = 'NeuralPromptBorder',
style = 'rounded',
text = {
top = title,
top_align = 'center',
},
},
win_options = {
winblend = 10,
winhighlight = 'Normal:Normal',
},
}, {
prompt = vim.g.neural.ui.prompt_icon .. ' ',
default_value = '',
on_close = function() end,
on_submit = function(value)
on_submit(value)
end,
})
input:mount()
input:on(Event.BufLeave, function()
input:unmount()
end)
end, { once = true })
-- Define exit keys
for _, v in ipairs(exit_keys) do
input:map(unpack(v))
end
for _, v in ipairs(exit_keys) do
input:map(unpack(v))
end
end

return UI
5 changes: 5 additions & 0 deletions plugin/neural.vim
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,17 @@ endif
command! -nargs=? Neural :call neural#Prompt(<q-args>)
" Stop Neural doing anything.
command! -nargs=0 NeuralStop :call neural#Stop()
" Create a completion buffer.
command! -nargs=? NeuralBuffer :call neural#buffer#CreateBuffer(<q-args>)
" Run completion on a Neural buffer.
command! -nargs=0 NeuralBufferRun :call neural#buffer#RunBuffer()
" Have Neural explain the visually selected lines.
command! -range NeuralExplain :call neural#explain#SelectedLines()

" <Plug> mappings for commands
nnoremap <silent> <Plug>(neural_prompt) :call neural#OpenPrompt()<Return>
nnoremap <silent> <Plug>(neural_stop) :call neural#Stop()<Return>
nnoremap <silent> <Plug>(neural_buffer) :call neural#buffer#CreateBuffer({})<Return>
vnoremap <silent> <Plug>(neural_explain) :NeuralExplain<Return>
" Set default keybinds for Neural unless we're told not to. We should almost
Expand Down
65 changes: 65 additions & 0 deletions test/vim/test_buffer.vader
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Before:
Save g:neural

runtime autoload/neural.vim

unlet! g:neural

let g:calls = []

function! neural#Run(prompt, options) abort
call add(g:calls, ['neural#Run', a:prompt, a:options])
endfunction

After:
unlet! g:calls

runtime autoload/neural/job.vim

Restore

Execute(It should create a neural buffer with default settings):
NeuralBuffer

AssertEqual bufexists('Neural Buffer'), 1
AssertEqual &filetype, 'neuralbuf'
" TODO: Assert if created with new or vertical new
AssertEqual &l:wrap, 1
AssertEqual &l:linebreak, 1

bdelete! Neural Buffer

Execute(It should create a neural buffer with arguments):
NeuralBuffer {"name": "Test Name", "create_mode": "horizontal", "wrap": v:false}

AssertEqual bufexists('Test Name'), 1
AssertEqual &filetype, 'neuralbuf'
" TODO: Assert if created with new or vertical new
AssertEqual &l:wrap, 0
AssertEqual &l:linebreak, 0

bdelete! Test Name

Given neuralbuf(A Neural buffer):
write a story
Execute(It should correctly run neural):
NeuralRun

AssertEqual
\ [
\ ['neural#Run', 'write a story', {'line': 1, 'echo': 0}],
\ ],
\ g:calls


" Plug mappings
Execute(The correct neural buffer keybinds should be configured):
redir => g:output
silent map <Plug>(neural_completion)
redir END

AssertEqual
\ [
\ 'n <Plug>(neural_completion) *@:NeuralRun<CR>',
\ ],
\ sort(split(g:output, "\n"))
13 changes: 13 additions & 0 deletions test/vim/test_config.vader
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ Execute(The default chatgpt settings should be correct):
\ },
\ get(g:neural.source, 'chatgpt')

Execute(The default neural buffer settings should be correct):
call neural#config#Load()
" call filter(g:neural.buffer, {key -> key =~ 'completion'})
"
" AssertEqual {'echo_enabled': v:true}, g:neural.ui
AssertEqual
\ {
\ 'completion_key': '\<C-CR>',
\ 'create_mode': 'vertical',
\ 'wrap': v:true,
\ },
\ get(g:neural, 'buffer')

Execute(Settings should be merged correctly):
for s:i in range(2)
if s:i == 0
Expand Down
Loading

0 comments on commit 0cbcb93

Please sign in to comment.