diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8c120c7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Augment Vim Plugin Changelog + +This file documents the notable changes for each stable version of the Augment +Vim plugin. The following list is not necessarily comprehensive, but should +include any changes that may impact the user experience. + +## 0.25.1 + +- Deprecate the `Enable` and `Disable` commands in favor of the + `g:augment_disable_completions` option which disables inline completions but + not the chat feature. See `:help g:augment_disable_completions` for more + details. +- Check for the `winfixbuf` option before setting it to avoid an error on older + versions of Vim. +- Perform a runtime compatibility check on startup to warn users if they are + running an unsupported version of Node.js. +- Improve the auth flow by significantly shortening the auth URL (addressing + issues with truncated URLs) and improving the error messages on failure. +- Add support for filepaths containing spaces. diff --git a/README.md b/README.md index 94ec538..4566463 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,35 @@ # Augment Vim & Neovim Plugin -> [!WARNING] -> This plugin is in early alpha development stage. Features may be incomplete, -> unstable, or change without notice. While basic functionality is available, -> you may encounter bugs, performance issues, or unexpected behavior. Current -> platform support is limited to MacOS and Linux, with Windows to be added at a -> later date. +## A Quick Tour -## Installation +Augment's Vim/Neovim plugin provides inline code completions and multi-turn +chat conversations specially tailored to your codebase. The plugin is designed +to work with any modern Vim or Neovim setup, and features the same underlying +context engine that powers our VSCode and IntelliJ plugins. -1. Both Vim and Neovim are supported, but the plugin may require a newer version - than what's installed on your system by default. +Once you've installed the plugin, tell Augment about your project by adding +[workspace folders](#workspace-folders) to your config file, and then sign-in +to the Augment service. You can now open a source file in your project, begin +typing, and you should receive context-aware code completions. Use tab to +accept a suggestion, or keep typing to refine the suggestions. To ask questions +about your codebase or request specific changes, use the `:Augment chat` command +to start a chat conversation. - - [Vim](https://github.com/vim/vim?tab=readme-ov-file#installation) version 9.1.0 or newer. +## Getting Started - - [Neovim](https://github.com/neovim/neovim/tree/master?tab=readme-ov-file#install-from-package), version - 0.10.0 or newer. +1. Sign up for a free trial of Augment at + [augmentcode.com](https://augmentcode.com). + +1. Ensure you have a compatible editor version installed. Both Vim and Neovim + are supported, but the plugin may require a newer version than what is + installed on your system by default. + + - For [Vim](https://github.com/vim/vim?tab=readme-ov-file#installation), + version 9.1.0 or newer. + + - For + [Neovim](https://github.com/neovim/neovim/tree/master?tab=readme-ov-file#install-from-package), + version 0.10.0 or newer. 1. Install [Node.js](https://nodejs.org/en/download/package-manager/all), version 22.0.0 or newer, which is a required dependency. @@ -48,6 +62,8 @@ { 'augmentcode/augment.vim' }, ``` +1. Add workspace folders to your config file. This is really essential to getting the most out of augment! See the [Workspace Folders](#workspace-folders) section for more information. + 1. Open Vim and sign in to Augment with the `:Augment signin` command. ## Basic Usage @@ -58,19 +74,62 @@ appear. The following commands are provided: ```vim -:Augment status " View the current status of the plugin -:Augment signin " Start the sign in flow -:Augment signout " Sign out of Augment -:Augment enable " Globally enable suggestions (on by default) -:Augment disable " Globally disable suggestions -:Augment log " View the plugin log -:Augment chat " Start a chat with Augment AI +:Augment status " View the current status of the plugin +:Augment signin " Start the sign in flow +:Augment signout " Sign out of Augment +:Augment log " View the plugin log +:Augment chat " Send a chat message to Augment AI +:Augment chat-new " Start a new chat conversation +:Augment chat-toggle " Toggle the chat panel visibility ``` +## Workspace Folders + +Workspace folders help Augment understand your codebase better by providing +additional context. Adding your project's root directory as a workspace folder +allows Augment to take advantage of context from across your project, rather +than just the currently open file, improving the accuracy and style of +completions and chat. + +You can configure workspace folders by setting +`g:augment_workspace_folders` in your vimrc: + +```vim +let g:augment_workspace_folders = ['/path/to/project', '~/another-project'] +``` + +Workspace folders can be specified using absolute paths or paths relative to +your home directory (~). Adding your project's root directory as a workspace +folder helps Augment generate completions that match your codebase's patterns +and conventions. + +Note: This option must be set before the plugin is loaded. + +After adding a workspace folder and restarting vim, the output of the +`:Augment status` command will include the syncing progress for the added +folder. + +If you want to ignore particular files or directories from your workspace, you +can create a `.augmentignore` file in the root of your workspace folder. This +file is treated similar to a `.gitignore` file. For example, to ignore all +files within the `node_modules` directory, you can add +the following lines to your `.augmentignore` file: + +``` +node_modules/ +``` + +For more information on how to use the `.augmentignore` file, see the [documentation](https://docs.augmentcode.com/setup-augment/sync). + + ## Chat -The chat command allows you to interact with Augment AI in a conversational -manner. You can use it in two ways: +Augment chat supports multi-turn conversations using your project's full +context. Once a conversation is started, subsequent chat exchanges will include +the history from the previous exchanges. This is useful for asking follow-up +questions or getting context-specific help. + +You can interact with chat in two ways: 1. Direct command with message: @@ -84,32 +143,20 @@ manner. You can use it in two ways: - Type `:Augment chat` followed by your question about the selection -The response will appear in a new buffer with markdown formatting. Note that -chat is currently limited to single-turn conversations - each chat command -starts a new conversation. +The response will appear in a separate chat buffer with markdown formatting. -## Workspace Folders - -Workspace folders help Augment understand your codebase better by providing -additional context. You can configure workspace folders by setting -`g:augment_workspace_folders` in your vimrc: +To start a new conversation, use the `:Augment chat-new` command. This will +clear the chat history from your context. -```vim -let g:augment_workspace_folders = ['/path/to/project', '~/another-project'] -``` - -Workspace folders can be specified using absolute paths or paths relative to -your home directory (~). Adding your project's root directory as a workspace -folder helps Augment generate completions that match your codebase's patterns -and conventions. - -Note: This option must be set before the plugin is loaded. +Use the `:Augment chat-toggle` command to open and close the chat panel. When +the chat panel is closed, the chat conversation will be preserved and can be +reopened with the same command. ## Alternate Keybinds By default, tab is used to accept a suggestion. If you want to use a different key, create a mapping that calls `augment#Accept()`. The function -takes an optional arugment used to specify the fallback text to insert if no +takes an optional argument used to specify the fallback text to insert if no suggestion is available. ```vim @@ -121,14 +168,63 @@ inoremap call augment#Accept() inoremap call augment#Accept("\n") ``` +or in neovim + +```lua +-- Use Ctrl-Y to accept a suggestion +vim.keymap.set('i', '', 'call augment#Accept()', { noremap = true }) +-- Use enter to accept a suggestion, falling back to a newline if no suggestion is available +vim.keymap.set('i', '', 'call augment#Accept()', { noremap = true }) +``` + The default tab mapping can be disabled by setting `g:augment_disable_tab_mapping = v:true` before the plugin is loaded. +Completions can be disabled entirely by setting +`g:augment_disable_completions = v:true` in your vimrc or at any time during +editing. + If another plugin uses tab in insert mode, the Augment tab mapping may be overridden depending on the order in which the plugins are loaded. If tab isn't working for you, the `imap ` command can be used to check if the mapping is present. +## FAQ + +**Q: I'm not seeing any completions. Is the plugin working?** + +A: You may want to first check the output of the `:Augment status` command. +This command will show the current status of the plugin, including whether +you're signed in and whether your workspace folders are synced. If you're not +signed in, you'll need to sign in using the `:Augment signin` command. If those +are not indicating a problem, you can check the plugin log using the `:Augment +log` command. This will show any errors that may have occurred. + +**Q: Can I create shortcuts for the Augment commands?** + +A: Absolutely! You can create mappings for any of the Augment commands. For +example, to create a shortcut for the `:Augment chat*` commands, you can add the +following to your vimrc: + +```vim +nnoremap ac :Augment chat +vnoremap ac :Augment chat +nnoremap an :Augment chat-new +nnoremap at :Augment chat-toggle +``` + +**Q: My workspace is taking a long time to sync. What should I do?** + +A: It may take a while to sync if you have a very large codebase that has not +been synced before. It's also not uncommon to inadvertenly include a large +directory like `node_modules/`. You can use `:Augment status` to see the +progress of the sync. If the sync is making progress but just slow, it may be +worth checking if you have a large directory that you don't need to sync. You +can add these directories to your `.augmentignore` file to exclude it from the +sync. If you're still having trouble, please file a github issue with a +description of the problem and include the output of `:Augment log`. + + ## Licensing and Distribution This repository includes two main components: @@ -142,4 +238,4 @@ For details on usage restrictions, refer to the [LICENSE.md](LICENSE.md) file. We encourage users to report any bugs or issues directly to us. Please use the [Issues](https://github.com/augmentcode/augment.vim/issues) section of this repository to share your feedback. -For any other questions, feel free to reach out to support@augmentcode.com. +For any other questions, feel free to reach out to [Augment Support](https://support.augmentcode.com/). diff --git a/autoload/augment.vim b/autoload/augment.vim index fd2eb7d..befdfc1 100644 --- a/autoload/augment.vim +++ b/autoload/augment.vim @@ -23,6 +23,11 @@ function! s:OpenBuffer() abort return endif + " Ignore non-file buffers + if &buftype != '' + return + endif + let client = augment#client#Client() if has('nvim') call luaeval('require("augment").open_buffer(_A[1], _A[2])', [client.client_id, bufnr('%')]) @@ -46,6 +51,11 @@ function! s:UpdateBuffer() abort return endif + " Ignore non-file buffers + if &buftype != '' + return + endif + " The nvim lsp client does this automatically if !has('nvim') " Only send a change notification if the buffer has changed (as @@ -73,8 +83,13 @@ function! s:RequestCompletion() abort return endif - " Don't send a request if disabled - if exists('g:augment_enabled') && !g:augment_enabled + " Ignore non-file buffers + if &buftype != '' + return + endif + + " Don't send a request if completions are disabled + if exists('g:augment_disable_completions') && g:augment_disable_completions return endif @@ -85,7 +100,16 @@ function! s:RequestCompletion() abort endif let b:_augment_comp_tick = b:changedtick - let uri = 'file://' . expand('%:p') + if has('nvim') + " NOTE(mpauly): On neovim, we use the built-in lsp client which + " requires the uri to be in the format defined by + " vim.uri_from_fname(). There isn't a straightforward way to format + " the uri on vim and it isn't causing any issues, so punting on it for + " now. + let uri = v:lua.vim.uri_from_fname(expand('%:p')) + else + let uri = 'file://' . expand('%:p') + endif let text = join(getline(1, '$'), "\n") " TODO: remove version-- we use it elsewhere but it's not in the spec call augment#client#Client().Request('textDocument/completion', { @@ -129,15 +153,21 @@ function! s:CommandSignOut(...) abort call augment#client#Client().Request('augment/logout', {}) endfunction +" NOTE: The enable/disable commands are deprecated function! s:CommandEnable(...) abort - let g:augment_enabled = v:true + call augment#DisplayError('The `Enable` and `Disable` commands are deprecated in favor of the `g:augment_disable_completions` option. See `:help g:augment_disable_completions` for more details.') endfunction function! s:CommandDisable(...) abort - let g:augment_enabled = v:false + call augment#DisplayError('The `Enable` and `Disable` commands are deprecated in favor of the `g:augment_disable_completions` option. See `:help g:augment_disable_completions` for more details.') endfunction function! s:CommandStatus(...) abort + if !exists('g:augment_initialized') || !g:augment_initialized + call augment#DisplayError('The Augment plugin failed to initialize. See ":Augment log" for more details.') + return + endif + if !s:IsRunning() echohl WarningMsg echo s:NOT_RUNNING_MSG @@ -149,13 +179,6 @@ function! s:CommandStatus(...) abort endfunction function! s:CommandChat(range, args) abort - if exists('g:augment_enabled') && !g:augment_enabled - echohl WarningMsg - echo 'Augment: Not enabled. Run ":Augment enable" to enable the plugin.' - echohl None - return - endif - if !s:IsRunning() echohl WarningMsg echo s:NOT_RUNNING_MSG @@ -171,6 +194,9 @@ function! s:CommandChat(range, args) abort let selected_text = '' endif + let uri = augment#chat#GetUri() + let history = augment#chat#GetHistory() + " Use the message from the additional command arguments if provided, or " prompt the user for a message let message = empty(a:args) ? input('Message: ') : a:args @@ -182,21 +208,18 @@ function! s:CommandChat(range, args) abort return endif - " Create new buffer for chat response - let chat_bufname = 'AugmentChat-' . strftime("%Y%m%d-%H%M%S") - let current_win = bufwinid(bufnr('%')) - call augment#chat#CreateBuffer(chat_bufname) - call win_gotoid(current_win) + call augment#chat#OpenChatPanel() + call augment#chat#AppendMessage(message) call augment#log#Info( - \ 'Making chat request in buffer ' . chat_bufname - \ . ' with selected_text="' . selected_text + \ 'Making chat request with file=' . uri + \ . ' selected_text="' . selected_text \ . '"' . ' message="' . message . '"') let params = { \ 'textDocumentPosition': { \ 'textDocument': { - \ 'uri': 'file://' . expand('%:p'), + \ 'uri': uri, \ }, \ 'position': { \ 'line': line('.') - 1, @@ -204,17 +227,27 @@ function! s:CommandChat(range, args) abort \ }, \ }, \ 'message': message, - \ 'partialResultToken': chat_bufname, \ } - " Add selected text if available + " Add selected text and history if available if !empty(selected_text) let params['selectedText'] = selected_text endif + if !empty(history) + let params['history'] = history + endif call augment#client#Client().Request('augment/chat', params) endfunction +function! s:CommandChatNew(range, args) abort + call augment#chat#Reset() +endfunction + +function! s:CommandChatToggle(range, args) abort + call augment#chat#Toggle() +endfunction + " Handle user commands let s:command_handlers = { \ 'log': function('s:CommandLog'), @@ -224,6 +257,8 @@ let s:command_handlers = { \ 'disable': function('s:CommandDisable'), \ 'status': function('s:CommandStatus'), \ 'chat': function('s:CommandChat'), + \ 'chat-new': function('s:CommandChatNew'), + \ 'chat-toggle': function('s:CommandChatToggle'), \ } function! augment#Command(range, args) abort range @@ -232,7 +267,14 @@ function! augment#Command(range, args) abort range return endif + " If the plugin failed to initialize, only allow status and log commands let command = split(a:args)[0] + if (!exists('g:augment_initialized') || !g:augment_initialized) + \ && command !=# 'status' && command !=# 'log' + call augment#DisplayError('The Augment plugin failed to initialize. Only `:Augment status` and `:Augment log` commands are available.') + return + endif + for [name, Handler] in items(s:command_handlers) " Note that ==? is case-insensitive comparison if command ==? name @@ -261,6 +303,7 @@ endfunction function! augment#OnBufEnter() abort call s:OpenBuffer() + call augment#chat#SaveUri() endfunction function! augment#OnTextChanged() abort @@ -296,3 +339,22 @@ function! augment#Accept(...) abort call feedkeys(fallback, 'nt') endif endfunction + +" Display an error message to the user in addition to logging it +function! augment#DisplayError(message) abort + " If we have already entered the editor, display the error message + " immediately. Otherwise, wait for VimEnter. + if v:vim_did_enter + echohl ErrorMsg | echom 'Augment: ' . a:message | echohl None + else + " Shadow the message argument with a script-local variable. This means + " that subsequent calls will override the previous message, which + " should be fine for our use case. + let s:error_message = a:message + augroup augment_error + autocmd! + autocmd VimEnter * echohl ErrorMsg | echom 'Augment: ' . s:error_message | echohl None + augroup END + endif + call augment#log#Error(a:message) +endfunction diff --git a/autoload/augment/chat.vim b/autoload/augment/chat.vim index 29b46a0..a3a9acd 100644 --- a/autoload/augment/chat.vim +++ b/autoload/augment/chat.vim @@ -3,6 +3,158 @@ " Utilities for chat +function! s:ResetChatContents() abort + let chat_buf = bufnr('AugmentChatHistory') + if chat_buf == -1 + call augment#log#Error('Chat reset failed: Could not find chat history buffer') + return + endif + + call setbufvar(chat_buf, '&modifiable', v:true) + silent call deletebufline(chat_buf, 1, '$') + call augment#chat#AppendText('# Augment Chat History' + \ . "\n\n" + \ . '`:Augment chat` Send a chat message in the current conversation' + \ . "\n" + \ . '`:Augment chat-new` Start a new conversation' + \ . "\n" + \ . '`:Augment chat-toggle` Toggle the chat panel visibility' + \ . "\n\n") +endfunction + +function! augment#chat#Toggle() abort + let chat_id = bufwinid('AugmentChatHistory') + if chat_id == -1 + call augment#chat#OpenChatPanel() + else + " Don't close if it's the last window + if winnr('$') > 1 + call win_execute(chat_id, 'close') + endif + endif +endfunction + +function! augment#chat#OpenChatPanel() abort + let current_win = win_getid() + + " Check if the panel already exists and has been setup + if bufexists('AugmentChatHistory') && !getbufvar('AugmentChatHistory', '&modifiable') + if bufwinid('AugmentChatHistory') == -1 + botright 80vnew AugmentChatHistory + endif + call win_gotoid(current_win) + return + endif + + " Open a buffer for the chat history with a width of 80 characters + botright 80vnew AugmentChatHistory + setlocal buftype=nofile " Buffer will never be written to a file + setlocal nomodifiable " Prevent any modifications + setlocal noswapfile " Don't create a swapfile + " NOTE(mpauly): winfixbuf is not available in some subversions of vim 9.1 + if exists('&winfixbuf') + setlocal winfixbuf " Keep buffer in window when splitting + endif + setlocal bufhidden=hide " When buffer is abandoned, hide it + setlocal nobuflisted " Hide from :ls + setlocal wrap " Wrap long lines + setlocal linebreak " Wrap at word boundaries + setlocal filetype=markdown " Use markdown syntax highlighting + setlocal nonumber " Hide line numbers + setlocal norelativenumber " Hide relative line numbers + setlocal signcolumn=no " Hide sign column + setlocal nocursorline " Disable cursor line highlighting + setlocal nospell " Disable spell checking + setlocal nofoldenable " Disable folding + setlocal textwidth=0 " Disable text width limit + setlocal scrolloff=0 " Disable scrolloff + + " Add the chat header to the buffer + call s:ResetChatContents() + + " TODO(AU-6480): create another buffer for the chat input + " new AugmentChatInput + + call win_gotoid(current_win) +endfunction + +function! augment#chat#Reset() abort + call s:ResetChatContents() + call s:ResetHistory() +endfunction + +function! s:ResetHistory() abort + let g:_augment_chat_history = [] +endfunction + +function! augment#chat#AppendText(text) abort + let chat_buf = bufnr('AugmentChatHistory') + if chat_buf == -1 + call augment#log#Error('Chat append failed: Could not find chat history buffer') + return + endif + + let lines = split(a:text, "\n", v:true) + let last_line = getbufline(chat_buf, '$')[0] + + call setbufvar(chat_buf, '&modifiable', v:true) + call setbufline(chat_buf, '$', last_line . lines[0]) + call appendbufline(chat_buf, '$', lines[1:]) + call setbufvar(chat_buf, '&modifiable', v:false) +endfunction + +function! augment#chat#AppendMessage(message) abort + " If not the first message, scroll to the bottom + let chat_id = bufwinid('AugmentChatHistory') + if !empty(augment#chat#GetHistory()) && chat_id != -1 + let command = "call winrestview({'lnum': line('$'), 'topline': line('$')})" + call win_execute(chat_id, command) + endif + + let message_text = '================================================================================' + \ . "\n\n" + \ . "\t*You*" + \ . "\n\n" + \ . a:message + \ . "\n\n" + \ . '--------------------------------------------------------------------------------' + \ . "\n\n" + \ . "\t*Augment*" + \ . "\n\n" + call augment#chat#AppendText(message_text) +endfunction + +function! augment#chat#AppendHistory(request_message, response_text, request_id) abort + if !exists('g:_augment_chat_history') + let g:_augment_chat_history = [] + endif + call add(g:_augment_chat_history, { + \ 'request_message': a:request_message, + \ 'response_text': a:response_text, + \ 'request_id': a:request_id, + \ }) +endfunction + +function! augment#chat#GetHistory() abort + if exists('g:_augment_chat_history') + return g:_augment_chat_history + endif + return [] +endfunction + +function! augment#chat#SaveUri() abort + if bufname('%') !=# 'AugmentChatHistory' + let g:_augment_current_uri = 'file://' . expand('%:p') + endif +endfunction + +function! augment#chat#GetUri() abort + if exists('g:_augment_current_uri') + return g:_augment_current_uri + endif + return 'file://' . expand('%:p') +endfunction + function! s:GetBufSelection(line_start, col_start, line_end, col_end) abort if a:line_start == a:line_end return getline(a:line_start)[a:col_start - 1:a:col_end - 1] @@ -63,15 +215,3 @@ function! augment#chat#GetSelectedText() abort let [line_end, col_end] = getpos("'>")[1:2] return s:GetBufSelection(line_start, col_start, line_end, col_end) endfunction - -function! augment#chat#CreateBuffer(bufname) abort - botright vnew - setlocal buftype=nofile - setlocal bufhidden=hide - setlocal noswapfile - setlocal wrap - setlocal linebreak - execute 'file ' . a:bufname - setlocal readonly - setlocal filetype=markdown -endfunction diff --git a/autoload/augment/client.vim b/autoload/augment/client.vim index c51d0b1..d52f042 100644 --- a/autoload/augment/client.vim +++ b/autoload/augment/client.vim @@ -3,15 +3,27 @@ " Client for interacting with the server process +" Custom LSP response error codes +let s:AUGMENT_ERROR_UNAUTHORIZED = 401 + let s:client = {} -" If provided, launch the server from a user-provided command -if exists('g:augment_job_command') - let s:job_command = g:augment_job_command -else - let server_file = expand(':h:h:h') . '/dist/server.js' - let s:job_command = ['node', server_file, '--stdio'] -endif +function! augment#client#GetJobCommand() abort + " If provided, launch the server from a user-provided command + if exists('g:augment_job_command') + return g:augment_job_command + endif + + let server_file = expand('