diff --git a/.gitignore b/.gitignore index dbdd306..8df500d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /env __pycache__ tags +# pyenv +.python-version diff --git a/autoload/neural.vim b/autoload/neural.vim index 5daa41a..5a55ed0 100644 --- a/autoload/neural.vim +++ b/autoload/neural.vim @@ -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 @@ -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)}) diff --git a/autoload/neural/buffer.vim b/autoload/neural/buffer.vim new file mode 100644 index 0000000..2f66785 --- /dev/null +++ b/autoload/neural/buffer.vim @@ -0,0 +1,83 @@ +" Author: Anexon +" 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 diff --git a/autoload/neural/config.vim b/autoload/neural/config.vim index 0e14f53..dc592d3 100644 --- a/autoload/neural/config.vim +++ b/autoload/neural/config.vim @@ -17,6 +17,11 @@ let s:defaults = { \ 'animated_sign_enabled': v:true, \ 'echo_enabled': v:true, \ }, +\ 'buffer': { +\ 'completion_key': '\', +\ 'create_mode': 'vertical', +\ 'wrap': v:true, +\ }, \ 'source': { \ 'openai': { \ 'api_key': '', diff --git a/ftplugin/neuralbuf.vim b/ftplugin/neuralbuf.vim new file mode 100644 index 0000000..cd8232f --- /dev/null +++ b/ftplugin/neuralbuf.vim @@ -0,0 +1,18 @@ +" Author: Anexon +" Description: Neural Buffer for interacting with neural sources directly. + +call neural#config#Load() + +command! -buffer -nargs=0 NeuralRun :call neural#buffer#RunBuffer() + +nnoremap (neural_completion) :NeuralRun + +" Keybindings of Neural Buffer +if exists('*keytrans') && exists('g:neural.buffer.completion_key') + execute 'nnoremap ' . keytrans(g:neural.buffer.completion_key) . ' (neural_completion)' + execute 'inoremap ' . keytrans(g:neural.buffer.completion_key) . ' (neural_completion)' +else + nnoremap (neural_completion) + inoremap (neural_completion) +endif + diff --git a/lua/neural.lua b/lua/neural.lua index 9ad7258..0d3fc3d 100644 --- a/lua/neural.lua +++ b/lua/neural.lua @@ -1,5 +1,3 @@ -local next = next - -- External dependencies local UI = {} local AnimatedSign = {} diff --git a/lua/neural/ui.lua b/lua/neural/ui.lua index 1009800..b3c0edf 100644 --- a/lua/neural/ui.lua +++ b/lua/neural/ui.lua @@ -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', - '', - function(_) - vim.api.nvim_command(':q') - end, - { noremap = true }, - }, - { - 'i', - '', - function(_) - vim.api.nvim_command(':q') - end, - { noremap = true }, - }, - { - 'i', - '', - 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', '', + function(_) + vim.api.nvim_command(':q') + end, {noremap = true}, + }, + {'i', '', + function(_) + vim.api.nvim_command(':q') + end, {noremap = true}, + }, + {'i', '', + 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 diff --git a/plugin/neural.vim b/plugin/neural.vim index 5087deb..dccdd55 100644 --- a/plugin/neural.vim +++ b/plugin/neural.vim @@ -33,12 +33,17 @@ endif command! -nargs=? Neural :call neural#Prompt() " Stop Neural doing anything. command! -nargs=0 NeuralStop :call neural#Stop() +" Create a completion buffer. +command! -nargs=? NeuralBuffer :call neural#buffer#CreateBuffer() +" 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() " mappings for commands nnoremap (neural_prompt) :call neural#OpenPrompt() nnoremap (neural_stop) :call neural#Stop() +nnoremap (neural_buffer) :call neural#buffer#CreateBuffer({}) vnoremap (neural_explain) :NeuralExplain " Set default keybinds for Neural unless we're told not to. We should almost diff --git a/test/vim/test_buffer.vader b/test/vim/test_buffer.vader new file mode 100644 index 0000000..e0b6d34 --- /dev/null +++ b/test/vim/test_buffer.vader @@ -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 (neural_completion) + redir END + + AssertEqual + \ [ + \ 'n (neural_completion) *@:NeuralRun', + \ ], + \ sort(split(g:output, "\n")) diff --git a/test/vim/test_config.vader b/test/vim/test_config.vader index db31526..c6af86b 100644 --- a/test/vim/test_config.vader +++ b/test/vim/test_config.vader @@ -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': '\', + \ '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 diff --git a/test/vim/test_neural.vader b/test/vim/test_neural.vader index d607fdd..75001bc 100644 --- a/test/vim/test_neural.vader +++ b/test/vim/test_neural.vader @@ -2,7 +2,7 @@ Before: Save g:neural " Load modules so we can mock the fuctions. - runtime autoload/neural/job.vim + runtime autoload/neural.vim unlet! g:neural let g:job_id = 0 diff --git a/test/vim/vimrc b/test/vim/vimrc index 47e8c99..1e5b440 100644 --- a/test/vim/vimrc +++ b/test/vim/vimrc @@ -14,5 +14,6 @@ if !has('win32') endif set nocompatible +filetype plugin on " The encoding must be explicitly set for tests for Windows. execute 'set encoding=utf-8'