Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
.coverage
.directory
.tox
*.pyc
.tox-docker
/env
__pycache__
tags
# pyenv
.python-version
*.pyc
*.egg-info
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10.18
37 changes: 25 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,39 @@ RUN apk --update add $PACKAGES && \
rm -rf /var/cache/apk/* /tmp/* /var/tmp/*

# Install tools for Python testing.
ENV PATH /root/.pyenv/shims:/root/.pyenv/bin:$PATH
# We need --ignore-installed to ignore the `packaging` package version.
RUN pip install --ignore-installed tox==4.4.5
RUN curl https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash \
&& eval "$(pyenv init -)" \
&& eval "$(pyenv virtualenv-init -)" \
&& pyenv install 3.7 \
&& pyenv install 3.10 \
&& ln -s /root/.pyenv/versions/3.7.*/bin/python3.7 /root/.pyenv/bin/python3.7 \
&& ln -s /root/.pyenv/versions/3.10.*/bin/python3.10 /root/.pyenv/bin/python3.10
ENV PATH=/root/.pyenv/shims:/root/.pyenv/bin:$PATH

# Switch to the /root dir copy the .python-version from the project.
WORKDIR /root

# Install tools for Vim testing.
# We have a layer here so we rebuild Vim and Neovim less frequently.
# Installing the Vim versions is the slowest build step.
RUN install_vim -tag v8.0.0027 -build \
-tag v9.0.0297 -build \
-tag neovim:v0.8.0 -build
# Install vint with Python 3.10 to avoid `packaging` issues.
RUN python3.10 -m pip install vim-vint==0.3.21
RUN git clone https://github.com/junegunn/vader.vim vader && \
cd vader && git checkout c6243dd81c98350df4dec608fa972df98fa2a3af

# Copy project files into the project for dependencies and such.
COPY .python-version /root/

# Install the Python version we need with uv.
# We have a layer here so we rebuild Python and install uv less frequently.
# Installing Python with uv is slower than updating dependencies, but much
# faster than installing the Vim and Neovim versions.
RUN curl https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash \
&& eval "$(pyenv init -)" \
&& eval "$(pyenv virtualenv-init -)" \
&& pyenv install \
&& pip install uv

# Sync dependencies and install the Python dependencies we need.
# vim-vint is included here for running the Vim lint steps.
# We have a layer here that's very fast.
COPY pyproject.toml uv.lock /root/
RUN uv sync --locked --no-install-project

ARG GIT_VERSION
LABEL Version=${GIT_VERSION}
LABEL Name=denseanalysis/neural
42 changes: 32 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

[![Vim](https://img.shields.io/badge/VIM-%2311AB00.svg?style=for-the-badge&logo=vim&logoColor=white)](https://www.vim.org/) [![Neovim](https://img.shields.io/badge/NeoVim-%2357A143.svg?&style=for-the-badge&logo=neovim&logoColor=white)](https://neovim.io/) [![CI](https://img.shields.io/github/actions/workflow/status/dense-analysis/neural/main.yml?branch=main&label=CI&logo=github&style=for-the-badge)](https://github.com/dense-analysis/neural/actions?query=event%3Apush+workflow%3ACI+branch%3Amain++) [![Join the Dense Analysis Discord server](https://img.shields.io/badge/chat-Discord-5865F2?style=for-the-badge&logo=appveyor)](https://discord.gg/5zFD6pQxDk)

A ChatGPT Vim plugin, an OpenAI Neovim plugin, and so much more! Neural integrates various machine learning tools so you can let AI write code for you in Vim/Neovim, among other helpful things.
A Neovim/Vim coding agent plugin. Neural integrates various machine learning
tools so you can let AI write code for you in Neovim/Vim, among other helpful
things. Use OpenAI's APIs made famous with ChatGPT, in Vim.

## 🌟 Features

Expand All @@ -12,7 +14,7 @@ A ChatGPT Vim plugin, an OpenAI Neovim plugin, and so much more! Neural integrat
* Easily ask AI to explain code or paragraphs `:NeuralExplain`
* Compatible with Vim 8.0+ & Neovim 0.8+
* Supported on Linux, Mac OSX, and Windows
* Only dependency is Python 3.7+
* Only dependency is Python 3.10+ (required for security and libraries)

Experience lightning-fast code generation and completion with asynchronous
streaming.
Expand Down Expand Up @@ -76,7 +78,7 @@ Plugin 'dense-analysis/neural'
## 🚀 Usage

You will need to configure a third party machine learning tool for Neural to
interact with. OpenAI is Neural's default data source, and one of the easiest
interact with. OpenAI is Neural's default data provider, and one of the easiest
to configure.

You will need to obtain an [OpenAI API key](https://beta.openai.com/signup/).
Expand All @@ -86,20 +88,24 @@ script or in a Lua config.
```vim
" Configure Neural like so in Vimscript
let g:neural = {
\ 'source': {
\ 'openai': {
\ 'api_key': $OPENAI_API_KEY,
\ 'providers': [
\ {
\ 'openai': {
\ 'api_key': $OPENAI_API_KEY,
\ },
\ },
\ },
\ ],
\}
```

```lua
-- Configure Neural like so in Lua
require('neural').setup({
source = {
openai = {
api_key = vim.env.OPENAI_API_KEY,
providers = {
{
openai = {
api_key = vim.env.OPENAI_API_KEY,
},
},
},
})
Expand Down Expand Up @@ -129,6 +135,22 @@ the stop command by default when you enter that key combination. The default
keybind can be disabled by setting `g:neural.set_default_keybinds` to any falsy
value. You can set a keybind to stop Neural by mapping to `<Plug>(neural_stop)`.

## 🛠️ Development

To get started developing Neural, you will need to run the following commands,
after first installing and correctly configuring
[pyenv](https://github.com/pyenv/pyenv).

```sh
pyenv install
pip install uv
uv sync
```

You should then get all of the linters and static analysis tools, and you can
run tests with `pytest` from virtualenv. We recommend using
[ALE](https://github.com/dense-analysis/ale) to run linters for this project.

## 📜 Acknowledgements

Neural was created by [Anexon](https://github.com/Angelchev), and is maintained
Expand Down
53 changes: 25 additions & 28 deletions autoload/neural.vim
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
" Author: Anexon <[email protected]>, w0rp <[email protected]>
" Description: The main autoload file for the Neural Vim plugin

" The location of Neural source scripts
let s:neural_script_dir = expand('<sfile>:p:h:h') . '/neural_providers'
" The location of Neural provider scripts
let s:neural_script_dir = expand('<sfile>:p:h:h') . '/src/neural/provider'
" Keep track of the current job.
let s:current_job = get(s:, 'current_job', 0)
" Keep track of the line the last request happened on.
Expand Down Expand Up @@ -117,8 +117,8 @@ function! s:HandleOutputEnd(buffer, job_data, exit_code) abort
endfunction

" Get the path to the executable for a script language.
function! s:GetScriptExecutable(source) abort
if a:source.script_language is# 'python'
function! s:GetScriptExecutable(provider) abort
if a:provider.script_language is# 'python'
let l:executable = ''

if has('win32')
Expand All @@ -133,7 +133,7 @@ function! s:GetScriptExecutable(source) abort
return l:executable
endif

throw 'Unknown script language: ' . a:source.script_language
throw 'Unknown script language: ' . a:provider.script_language
endfunction

" Escape a string suitably for each platform.
Expand Down Expand Up @@ -234,29 +234,26 @@ function! neural#PreProcess(buffer, input) abort
endfor
endfunction

function! s:LoadDataSource() abort
let l:selected = g:neural.selected
function! s:LoadProvider() abort
" TODO: Change message if nothing is selected.
let l:config = get(g:neural.providers, 0, {})
let l:type = get(l:config, 'type', '')

try
let l:source = function('neural#source#' . selected . '#Get')()
let l:provider = function('neural#provider#' . l:type . '#Get')()
catch /E117/
call neural#OutputErrorMessage('Invalid source: ' . l:selected)
call neural#OutputErrorMessage('Invalid provider: ' . l:type)

return
endtry

return l:source
endfunction

function! s:GetSourceInput(buffer, source, prompt) abort
let l:config = get(g:neural.source, a:source.name, {})
let l:provider.config = config

" If the config is not a Dictionary, throw it away.
if type(l:config) isnot v:t_dict
let l:config = {}
endif
return l:provider
endfunction

let l:input = {'config': l:config, 'prompt': a:prompt}
function! s:GetProviderInput(buffer, provider, prompt) abort
let l:input = {'config': a:provider.config, 'prompt': a:prompt}

" Pre-process input, such as modifying a prompt.
call neural#PreProcess(a:buffer, l:input)
Expand All @@ -265,13 +262,13 @@ function! s:GetSourceInput(buffer, source, prompt) abort
endfunction

function! neural#GetCommand(buffer) abort
let l:source = s:LoadDataSource()
let l:script_exe = s:GetScriptExecutable(l:source)
let l:provider = s:LoadProvider()
let l:script_exe = s:GetScriptExecutable(l:provider)
let l:command = neural#Escape(l:script_exe)
\ . ' ' . neural#Escape(l:source.script)
\ . ' ' . neural#Escape(l:provider.script)
let l:command = neural#job#PrepareCommand(a:buffer, l:command)

return [l:source, l:command]
return [l:provider, l:command]
endfunction

function! neural#Prompt(prompt) abort
Expand Down Expand Up @@ -300,8 +297,8 @@ function! neural#ViewPrompt(...) abort
" Take the first argument or nothing.
let l:prompt = get(a:000, 0, '')
let l:buffer = bufnr('')
let l:source = s:LoadDataSource()
let l:input = s:GetSourceInput(l:buffer, l:source, l:prompt)
let l:provider = s:LoadProvider()
let l:input = s:GetProviderInput(l:buffer, l:provider, l:prompt)

" no-custom-checks
echohl Question
Expand Down Expand Up @@ -330,7 +327,7 @@ function! neural#Run(prompt, options) abort
let l:moving_line -= 1
endif

let [l:source, l:command] = neural#GetCommand(l:buffer)
let [l:provider, l:command] = neural#GetCommand(l:buffer)
let l:job_data = {
\ 'moving_line': l:moving_line,
\ 'error_lines': [],
Expand All @@ -344,10 +341,10 @@ function! neural#Run(prompt, options) abort
\})

if l:job_id > 0
let l:input = s:GetSourceInput(l:buffer, l:source, a:prompt)
let l:input = s:GetProviderInput(l:buffer, l:provider, a:prompt)
call neural#job#SendRaw(l:job_id, json_encode(l:input) . "\n")
else
call neural#OutputErrorMessage('Failed to run ' . l:source.name)
call neural#OutputErrorMessage('Failed to run ' . l:provider.name)

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

scriptencoding utf-8

Expand All @@ -9,7 +9,7 @@ call neural#config#Load()
function! s:GetOptions(options_dict_string) abort
call neural#config#Load()

" TODO: Set buffer name based on source.
" TODO: Set buffer name based on provider.
let l:options = {
\ 'name': 'Neural Buffer',
\ 'create_mode': g:neural.buffer.create_mode,
Expand All @@ -33,6 +33,8 @@ function! s:GetOptions(options_dict_string) abort
endif
endif

Dump l:options

return l:options
endfunction

Expand All @@ -42,6 +44,10 @@ function! neural#buffer#CreateBuffer(options) abort
" TODO: Add auto incrementing buffer names instead of switching.
if bufexists(l:buffer_options.name)
execute 'buffer' bufnr(l:buffer_options.name)
setlocal filetype=neuralbuf
setlocal buftype=nofile
setlocal bufhidden=hide
setlocal noswapfile
else
if l:buffer_options.create_mode is# 'vertical'
vertical new
Expand Down
Loading
Loading