Full Code of kiddos/gemini.nvim for AI

master 6e8474b0a099 cached
16 files
44.2 KB
12.4k tokens
1 requests
Download .txt
Repository: kiddos/gemini.nvim
Branch: master
Commit: 6e8474b0a099
Files: 16
Total size: 44.2 KB

Directory structure:
gitextract_p6arm2ii/

├── .gitignore
├── .luacheckrs
├── LICENSE
├── Makefile
├── README.md
├── doc/
│   └── gemini.nvim.txt
├── lua/
│   ├── gemini/
│   │   ├── api.lua
│   │   ├── chat.lua
│   │   ├── completion.lua
│   │   ├── config.lua
│   │   ├── instruction.lua
│   │   ├── task.lua
│   │   └── util.lua
│   └── gemini.lua
└── tests/
    ├── gemini/
    │   └── api_spec.lua
    └── init.lua

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
__pycache__
*.db

lua/gemini/*.so
.cache
build

doc/tags


================================================
FILE: .luacheckrs
================================================
read_globals = { "vim" }


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Joseph

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
TESTS_INIT=tests/init.lua
TESTS_DIR=tests/

all:
	cmake -B build
	make -C build -j

clean:
	rm -rf build
	rm -rf lua/gemini/*.so


test:
	@nvim \
		--headless \
		--noplugin \
		-u ${TESTS_INIT} \
		-c "PlenaryBustedDirectory ${TESTS_DIR} { minimal_init = '${TESTS_INIT}' }"


.PHONY: all clean test


================================================
FILE: README.md
================================================
# gemini.nvim

This plugin try to interface Google's Gemini API into neovim.


## Features

- Code Complete
- Code Explain
- Unit Test Generation
- Code Review
- Hints
- Chat

### Code Complete
https://github.com/user-attachments/assets/11ae6719-4f3f-41db-8ded-56db20e6e9f4

https://github.com/user-attachments/assets/34c38078-a028-47d2-acb1-49e03d0b4330

### Do some changes
https://github.com/user-attachments/assets/c0a001a2-a5fe-469d-ae01-3468d05b041c




### Code Explain
https://github.com/user-attachments/assets/6b2492ee-7c70-4bbc-937b-27bfa50f8944

### Unit Test generation
https://github.com/user-attachments/assets/0620a8a4-5ea6-431d-ba17-41c7d553f742

### Code Review
https://github.com/user-attachments/assets/9100ab70-f107-40de-96e2-fb4ea749c014

### Hints
https://github.com/user-attachments/assets/a36804e9-073f-4e3e-9178-56b139fd0c62

### Chat
https://github.com/user-attachments/assets/d3918d2a-4cf7-4639-bc21-689d4225ba6d


## Installation

- install `curl`

```
sudo apt install curl
```





```shell
export GEMINI_API_KEY="<your API key here>"
```

* [lazy.nvim](https://github.com/folke/lazy.nvim)

```lua
{
  'kiddos/gemini.nvim',
  opts = {}
}
```


* [packer.nvim](https://github.com/wbthomason/packer.nvim)


```lua
use { 'kiddos/gemini.nvim', opts = {} }
```

## Settings

default setting

```lua
{
  model_config = {
    model_id = 'gemini-2.5-flash',
    temperature = 0.10,
    top_k = 128,
    response_mime_type = 'text/plain',
  },
  chat_config = {
    enabled = true,
  },
  hints = {
    enabled = true,
    hints_delay = 2000,
    insert_result_key = '<S-Tab>',
    get_prompt = function(node, bufnr)
      local code_block = vim.treesitter.get_node_text(node, bufnr)
      local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
      local prompt = [[
  In struction: Use 1 or 2 sentences to describe what the following {filetype} function does:

  ```{filetype}
  {code_block}
  ``]] .. '`'
      prompt = prompt:gsub('{filetype}', filetype)
      prompt = prompt:gsub('{code_block}', code_block)
      return prompt
    end
  },
  completion = {
    enabled = true,
    blacklist_filetypes = { 'help', 'qf', 'json', 'yaml', 'toml', 'xml' },
    blacklist_filenames = { '.env' },
    completion_delay = 800,
    insert_result_key = '<S-Tab>',
    move_cursor_end = true,
    can_complete = function()
      return vim.fn.pumvisible() ~= 1
    end,
    get_system_text = function()
      return "You are a coding AI assistant that autocomplete user's code."
        .. "\n* Your task is to provide code suggestion at the cursor location marked by <cursor></cursor>."
        .. '\n* Your response does not need to contain explaination.'
    end,
    get_prompt = function(bufnr, pos)
      local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
      local prompt = 'Below is the content of a %s file `%s`:\n'
          .. '```%s\n%s\n```\n\n'
          .. 'Suggest the most likely code at <cursor></cursor>.\n'
          .. 'Wrap your response in ``` ```\n'
          .. 'eg.\n```\n```\n\n'
      local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
      local line = pos[1]
      local col = pos[2]
      local target_line = lines[line]
      if target_line then
        lines[line] = target_line:sub(1, col) .. '<cursor></cursor>' .. target_line:sub(col + 1)
      else
        return nil
      end
      local code = vim.fn.join(lines, '\n')
      local abs_path = vim.api.nvim_buf_get_name(bufnr)
      local filename = vim.fn.fnamemodify(abs_path, ':.')
      prompt = string.format(prompt, filetype, filename, filetype, code)
      return prompt
    end
  },
  instruction = {
    enabled = true,
    menu_key = '<Leader><Leader><Leader>g',
    prompts = {
      {
        name = 'Unit Test',
        command_name = 'GeminiUnitTest',
        menu = 'Unit Test 🚀',
        get_prompt = function(lines, bufnr)
          local code = vim.fn.join(lines, '\n')
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
          local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
              .. 'Objective: Write unit test for the above snippet of code\n'
          return string.format(prompt, filetype, code)
        end,
      },
      {
        name = 'Code Review',
        command_name = 'GeminiCodeReview',
        menu = 'Code Review 📜',
        get_prompt = function(lines, bufnr)
          local code = vim.fn.join(lines, '\n')
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
          local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
              .. 'Objective: Do a thorough code review for the following code.\n'
              .. 'Provide detail explaination and sincere comments.\n'
          return string.format(prompt, filetype, code)
        end,
      },
      {
        name = 'Code Explain',
        command_name = 'GeminiCodeExplain',
        menu = 'Code Explain',
        get_prompt = function(lines, bufnr)
          local code = vim.fn.join(lines, '\n')
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
          local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
              .. 'Objective: Explain the following code.\n'
              .. 'Provide detail explaination and sincere comments.\n'
          return string.format(prompt, filetype, code)
        end,
      },
    }
  },
  task = {
    enabled = true,
    get_system_text = function()
      return 'You are an AI assistant that helps user write code.'
        .. '\n* You should output the new content for the Current Opened File'
    end,
    get_prompt = function(bufnr, user_prompt)
      local buffers = vim.api.nvim_list_bufs()
      local file_contents = {}

      for _, b in ipairs(buffers) do
        if vim.api.nvim_buf_is_loaded(b) then -- Only get content from loaded buffers
          local lines = vim.api.nvim_buf_get_lines(b, 0, -1, false)
          local abs_path = vim.api.nvim_buf_get_name(b)
          local filename = vim.fn.fnamemodify(abs_path, ':.')
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = b })
          local file_content = table.concat(lines, "\n")
          file_content = string.format("`%s`:\n\n```%s\n%s\n```\n\n", filename, filetype, file_content)
          table.insert(file_contents, file_content)
        end
      end

      local current_filepath = vim.api.nvim_buf_get_name(bufnr)
      current_filepath = vim.fn.fnamemodify(current_filepath, ":.")

      local context = table.concat(file_contents, "\n\n")
      return string.format('%s\n\nCurrent Opened File: %s\n\nTask: %s',
        context, current_filepath, user_prompt)
    end
    },
}
```


================================================
FILE: doc/gemini.nvim.txt
================================================
*gemini.nvim*    Google's Gemini neovim binding

INTRODUCTION                                    *gemini.nvim-intro*

This plugin try to interface Google's Gemini API into neovim.



INSTALLATION                                    *gemini.nvim-install*

install curl

sudo apt install curl

setup API key

export GEMINI_API_KEY="<your API key here>"

lazy.nvim:

{
  'kiddos/gemini.nvim',
  opts = {}
}



CONFIGURATION                                    *gemini.nvim-config*

{
  model_config = {
    completion_delay = 1000,
    model_id = api.MODELS.GEMINI_2_0_FLASH,
    temperature = 0.2,
    top_k = 20,
    max_output_tokens = 8196,
    response_mime_type = 'text/plain',
  },
  chat_config = {
    enabled = true,
  },
  hints = {
    enabled = true,
    hints_delay = 2000,
    insert_result_key = '<S-Tab>',
    get_prompt = function(node, bufnr)
      local code_block = vim.treesitter.get_node_text(node, bufnr)
      local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
      local prompt = "
  Instruction: Use 1 or 2 sentences to describe what the following {filetype} function does:
  
  ```{filetype}
  {code_block}
  ```",
      prompt = prompt:gsub('{filetype}', filetype)
      prompt = prompt:gsub('{code_block}', code_block)
      return prompt
    end
  }
  completion = {
    enabled = true,
    blacklist_filetypes = { 'help', 'qf', 'json', 'yaml', 'toml' },
    blacklist_filenames = { '.env' },
    completion_delay = 600,
    move_cursor_end = false,
    insert_result_key = '<S-Tab>',
    can_complete = function()
      return vim.fn.pumvisible() ~= 1
    end,
    get_system_text = function()
      return "You are a coding AI assistant that autocomplete user's code."
        .. "\n* Your task is to provide code suggestion at the cursor location marked by <cursor></cursor>."
        .. '\n* Do not wrap your code response in ```'
    end,
    get_prompt = function(bufnr, pos)
      local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
      local prompt = 'Below is the content of a %s file `%s`:\n'
          .. '```%s\n%s\n```\n\n'
          .. 'Suggest the most likely code at <cursor></cursor>.\n'
      local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
      local line = pos[1]
      local col = pos[2]
      local target_line = lines[line]
      if target_line then
        lines[line] = target_line:sub(1, col) .. '<cursor></cursor>' .. target_line:sub(col + 1)
      else
        return nil
      end
      local code = vim.fn.join(lines, '\n')
      local filename = vim.api.nvim_buf_get_name(bufnr)
      prompt = string.format(prompt, filetype, filename, filetype, code)
      return prompt
    end
  },
  instruction = {
    enabled = true,
    menu_key = '<C-o>',
    prompts = {
      {
        name = 'Unit Test',
        command_name = 'GeminiUnitTest',
        menu = 'Unit Test 🚀',
        get_prompt = function(lines, bufnr)
          local code = vim.fn.join(lines, '\n')
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
          local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
              .. 'Objective: Write unit test for the above snippet of code\n'
          return string.format(prompt, filetype, code)
        end,
      },
      {
        name = 'Code Review',
        command_name = 'GeminiCodeReview',
        menu = 'Code Review 📜',
        get_prompt = function(lines, bufnr)
          local code = vim.fn.join(lines, '\n')
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
          local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
              .. 'Objective: Do a thorough code review for the following code.\n'
              .. 'Provide detail explaination and sincere comments.\n'
          return string.format(prompt, filetype, code)
        end,
      },
      {
        name = 'Code Explain',
        command_name = 'GeminiCodeExplain',
        menu = 'Code Explain',
        get_prompt = function(lines, bufnr)
          local code = vim.fn.join(lines, '\n')
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
          local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
              .. 'Objective: Explain the following code.\n'
              .. 'Provide detail explaination and sincere comments.\n'
          return string.format(prompt, filetype, code)
        end,
      },
    },
  },
  task = {
    enabled = true,
    get_system_text = function()
      return 'You are an AI assistant that helps user write code.\n'
        .. 'Your output should be a code diff for git.'
    end,
    get_prompt = function(bufnr, user_prompt)
      local buffers = vim.api.nvim_list_bufs()
      local file_contents = {}

      for _, b in ipairs(buffers) do
        if vim.api.nvim_buf_is_loaded(b) then -- Only get content from loaded buffers
          local lines = vim.api.nvim_buf_get_lines(b, 0, -1, false)
          local filename = vim.api.nvim_buf_get_name(b)
          filename = vim.fn.fnamemodify(filename, ":.")
          local filetype = vim.api.nvim_get_option_value('filetype', { buf = b })
          local file_content = table.concat(lines, "\n")
          file_content = string.format("`%s`:\n\n```%s\n%s\n```\n\n", filename, filetype, file_content)
          table.insert(file_contents, file_content)
        end
      end

      local current_filepath = vim.api.nvim_buf_get_name(bufnr)
      current_filepath = vim.fn.fnamemodify(current_filepath, ":.")

      local context = table.concat(file_contents, "\n\n")
      return string.format('%s\n\nCurrent Opened File: %s\n\nTask: %s',
        context, current_filepath, user_prompt)
    end
  },
}



COMMANDS                                                     *gemini.nvim-commands*

:GeminiTask            ask Gemini to complete some task. this would use current opened buffer as context. this would also open up a diff pane in the current window

:GeminiApply           this would apply the diff result from :GeminiTask

:GeminiChat            ask Gemini to do something. this doesn't use any context

:GeminiCodeExplain     select some code and ask Gemini what does it do

:GeminiCodeReview      do some code review

:GeminiUnitTest        let Gemini write your unit test

:GeminiFunctionHint    give some function documentation



MAPPINGS                                                     *gemini.nvim-mappings*

<leader><leader><leader>g     open a popup menu to select some task to do

<S-Tab>                       confirm gemini's autocomplete


================================================
FILE: lua/gemini/api.lua
================================================
local uv = vim.loop or vim.uv

local M = {}

local API = "https://generativelanguage.googleapis.com/v1beta/models/";

M.MODELS = {
  GEMINI_3_FLASH_PREVIEW = 'gemini-3-flash-preview',
  GEMINI_FLASH_LATEST = 'gemini-flash-latest',
  GEMINI_3_1_PRO_PREVIEW = 'gemini-3.1-pro-preview',
  GEMINI_3_1_FLASH_LITE_PREVIEW = 'gemini-3.1-flash-lite-preview',
  GEMINI_FLASH_LITE_LATEST = 'gemini-flash-lite-latest',
  GEMINI_2_5_PRO = 'gemini-2.5-pro',
  GEMINI_2_5_FLASH = 'gemini-2.5-flash',
  GEMINI_2_5_FLASH_LITE = 'gemini-2.5-flash-lite',
  GEMINI_2_0_FLASH = 'gemini-2.0-flash',
  GEMINI_2_0_FLASH_LITE = 'gemini-2.0-flash-lite',
}

M.gemini_generate_content = function(user_text, system_text, model_name, generation_config, callback)
  local api_key = os.getenv("GEMINI_API_KEY")
  if not api_key then
    return ''
  end

  local api = API .. model_name .. ':generateContent?key=' .. api_key
  local contents = {
    {
      parts = {
        {
          text = user_text
        }
      }
    }
  }
  local data = {
    contents = contents,
    generationConfig = generation_config,
  }
  if system_text then
    data.systemInstruction = {
      parts = {
        {
          text = system_text,
        }
      }
    }
  end

  local json_text = vim.json.encode(data)
  local cmd = { 'curl', '--no-progress-meter', '-X', 'POST', api, '-H', 'Content-Type: application/json', '--data-binary', '@-' }
  local opts = { stdin = json_text }
  if callback then
    return vim.system(cmd, opts, callback)
  else
    return vim.system(cmd, opts)
  end
end

M.gemini_generate_content_stream = function(user_text, model_name, generation_config, callback)
  local api_key = os.getenv("GEMINI_API_KEY")
  if not api_key then
    return
  end

  if not callback then
    return
  end

  local api = API .. model_name .. ':streamGenerateContent?alt=sse&key=' .. api_key
  local data = {
    contents = {
      {
        parts = {
          {
            text = user_text
          }
        }
      }
    },
    generationConfig = generation_config,
  }
  local json_text = vim.json.encode(data)

  local stdin = uv.new_pipe()
  local stdout = uv.new_pipe()
  local stderr = uv.new_pipe()
  local options = {
    stdio = { stdin, stdout, stderr },
    args = { api, '-X', 'POST', '-s', '-H', 'Content-Type: application/json', '-d', json_text }
  }

  uv.spawn('curl', options, function(code, _)
    print("gemini chat finished exit code", code)
  end)

  local streamed_data = ''
  uv.read_start(stdout, function(err, data)
    if not err and data then
      streamed_data = streamed_data .. data

      local start_index = string.find(streamed_data, 'data:')
      local end_index = string.find(streamed_data, '\r')
      local json_text = ''
      while start_index and end_index do
        if end_index >= start_index then
          json_text = string.sub(streamed_data, start_index + 5, end_index - 1)
          callback(json_text)
        end
        streamed_data = string.sub(streamed_data, end_index + 1)
        start_index = string.find(streamed_data, 'data:')
        end_index = string.find(streamed_data, '\r')
      end
    end
  end)

end

return M


================================================
FILE: lua/gemini/chat.lua
================================================
local config = require('gemini.config')
local util = require('gemini.util')
local api = require('gemini.api')

local M = {}

M.setup = function()
  local model = config.get_config({ 'chat', 'model' })
  if not model or not model.model_id then
    return
  end

  vim.api.nvim_create_user_command('GeminiChat', M.start_chat, {
    force = true,
    desc = 'Google Gemini',
    nargs = 1,
  })
end

local context = {
  chat_winnr = nil,
  chat_number = 0,
}

local function get_bufnr(user_text)
  local conf = config.get_config({ 'chat' })
  if not conf then
    vim.api.nvim_command('tabnew')
    local bufnr = vim.api.nvim_get_current_buf()
    vim.api.nvim_set_option_value('ft', 'markdown', { buf = bufnr })
    return bufnr
  end

  local bufnr = nil
  if not context.chat_winnr or not vim.api.nvim_win_is_valid(context.chat_winnr) or conf.window.position == 'new_tab' then
    if conf.window.position == 'tab' or conf.window.position == 'new_tab' then
      vim.api.nvim_command('tabnew')
    elseif conf.window.position == 'left' then
      vim.api.nvim_command('vertical topleft split new')
      vim.api.nvim_win_set_width(0, conf.window.width or 80)
    elseif conf.window.position == 'right' then
      vim.api.nvim_command('rightbelow vnew')
      vim.api.nvim_win_set_width(0, conf.window.width or 80)
    end
    context.chat_winnr = vim.api.nvim_tabpage_get_win(0)
    bufnr = vim.api.nvim_win_get_buf(0)
  end
  vim.api.nvim_set_current_win(context.chat_winnr)
  bufnr = bufnr or vim.api.nvim_win_get_buf(0)
  vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr })
  vim.api.nvim_set_option_value('ft', 'markdown', { buf = bufnr })
  vim.api.nvim_buf_set_name(bufnr, 'Chat' .. context.chat_number .. ': ' .. user_text)

  return vim.api.nvim_win_get_buf(0)
end

M.start_chat = function(cxt)
  local user_text = cxt.args
  context.chat_number = context.chat_number + 1
  local bufnr = get_bufnr(user_text)
  local lines = { 'Generating response...' }
  vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)

  local generation_config = config.get_gemini_generation_config('chat')
  local text = ''
  local model_id = config.get_config({ 'chat', 'model', 'model_id' })
  api.gemini_generate_content_stream(user_text, model_id, generation_config, function(json_text)
    local model_response = vim.json.decode(json_text)
    model_response = util.table_get(model_response, { 'candidates', 1, 'content', 'parts', 1, 'text' })
    if not model_response then
      return
    end

    text = text .. model_response
    vim.schedule(function()
      lines = vim.split(text, '\n')
      vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
    end)
  end)
end

return M


================================================
FILE: lua/gemini/completion.lua
================================================
local config = require('gemini.config')
local util = require('gemini.util')
local api = require('gemini.api')

local M = {}

local context = {
  namespace_id = vim.api.nvim_create_namespace('gemini_completion'),
  completion = nil,
}

M.setup = function()
  local model = config.get_config({ 'completion', 'model' })
  if not model or not model.model_id then
    return
  end

  vim.api.nvim_create_autocmd('CursorMovedI', {
    callback = function()
      M.gemini_complete()
    end,
  })

  local trigger_completion_key = config.get_config({ 'completion', 'trigger_key' })
  if trigger_completion_key then
    vim.api.nvim_set_keymap('i', trigger_completion_key, '', {
      callback = function()
        M.gemini_complete()
      end,
    })
  end

  local insert_key = config.get_config({ 'completion', 'insert_result_key' }) or '<S-Tab>'
  vim.api.nvim_set_keymap('i', insert_key, '', {
    callback = function()
      M.insert_completion_result()
    end,
  })
end

local get_prompt_text = function(bufnr, pos)
  local get_prompt = config.get_config({ 'completion', 'get_prompt' })
  if not get_prompt then
    vim.notify('prompt function is not found', vim.log.levels.WARN)
    return nil
  end
  return get_prompt(bufnr, pos)
end

local function handle_result(win, pos, json_text)
  local model_response = vim.json.decode(json_text)
  local text = util.table_get(model_response, { 'candidates', 1, 'content', 'parts', 1, 'text' })
  if model_response ~= nil and #model_response > 0 then
    vim.schedule(function()
      if model_response then
        local code_blocks = util.strip_code(text)
        local response = vim.fn.join(code_blocks, '\n\n')
        M.show_completion_result(response, win, pos)
      end
    end)
  end

  local error_msg = util.table_get(model_response, { 'error', 'message' })
  if error_msg then
    vim.schedule(function()
      vim.api.nvim_echo({{ error_msg, 'WarningMsg' }}, false, {})
    end)
  end
end

M._gemini_complete = function()
  local bufnr = vim.api.nvim_get_current_buf()
  local win = vim.api.nvim_get_current_win()
  local pos = vim.api.nvim_win_get_cursor(win)
  local user_text = get_prompt_text(bufnr, pos)
  if not user_text then
    return
  end

  local system_text = nil
  local get_system_text = config.get_config({ 'completion', 'get_system_text' })
  if get_system_text then
    system_text = get_system_text()
  end

  local generation_config = config.get_gemini_generation_config('completion')
  local model_id = config.get_config({ 'completion', 'model', 'model_id' })
  api.gemini_generate_content(user_text, system_text, model_id, generation_config, function(result)
    local json_text = result.stdout
    if json_text and #json_text > 0 then
      handle_result(win, pos, json_text)
    end
  end)
end

M.gemini_complete = util.debounce(function()
  local blacklist_filetypes = config.get_config({ 'completion', 'blacklist_filetypes' }) or {}
  local blacklist_filenames = config.get_config({ 'completion', 'blacklist_filenames' }) or {}

  local buf = vim.api.nvim_get_current_buf()
  local filetype = vim.api.nvim_get_option_value('filetype', { buf = buf })
  local filename = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":t")
  if util.is_blacklisted(blacklist_filetypes, filetype) or util.is_blacklisted(blacklist_filenames, filename) then
    return
  end

  if vim.fn.mode() ~= 'i' then
    return
  end

  local can_complete = config.get_config({'completion', 'can_complete'})
  if not can_complete or not can_complete() then
    return
  end

  print('-- gemini complete --')
  M._gemini_complete()
end, config.get_config({ 'completion', 'completion_delay' }) or 1000)

M.show_completion_result = function(result, win_id, pos)
  local win = vim.api.nvim_get_current_win()
  if win ~= win_id then
    return
  end

  local current_pos = vim.api.nvim_win_get_cursor(win)
  if current_pos[1] ~= pos[1] or current_pos[2] ~= pos[2] then
    return
  end

  if vim.fn.mode() ~= 'i' then
    return
  end

  local can_complete = config.get_config({'completion', 'can_complete'})
  if not can_complete or not can_complete() then
    return
  end

  local bufnr = vim.api.nvim_get_current_buf()
  local options = {
    id = 1,
    virt_text = {},
    virt_lines = {},
    hl_mode = 'combine',
    virt_text_pos = 'inline',
  }

  local content = result:match("^%s*(.-)%s*$")
  for i, l in pairs(vim.split(content, '\n')) do
    if i == 1 then
      options.virt_text[1] = { l, 'Comment' }
    else
      options.virt_lines[i - 1] = { { l, 'Comment' } }
    end
  end
  local row = pos[1]
  local col = pos[2]
  local id = vim.api.nvim_buf_set_extmark(bufnr, context.namespace_id, row - 1, col, options)

  context.completion = {
    content = content,
    row = row,
    col = col,
    bufnr = bufnr,
  }

  vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI', 'InsertLeavePre' }, {
    buffer = bufnr,
    callback = function()
      context.completion = nil
      vim.api.nvim_buf_del_extmark(bufnr, context.namespace_id, id)
      vim.api.nvim_command('redraw')
    end,
    once = true,
  })
end

M.insert_completion_result = function()
  if not context.completion then
    return
  end

  local bufnr = vim.api.nvim_get_current_buf()
  if not context.completion.bufnr == bufnr then
    return
  end

  local row = context.completion.row - 1
  local col = context.completion.col
  local first_line = vim.api.nvim_buf_get_lines(0, row, row + 1, false)[1]
  local lines = vim.split(context.completion.content, '\n')
  lines[1] = string.sub(first_line, 1, col) .. lines[1] .. string.sub(first_line, col + 1)
  vim.api.nvim_buf_set_lines(bufnr, row, row + 1, false, lines)

  if config.get_config({ 'completion', 'move_cursor_end' }) == true then
    local new_row = row + #lines
    local new_col = #vim.api.nvim_buf_get_lines(0, new_row - 1, new_row, false)[1]
    vim.api.nvim_win_set_cursor(0, { new_row, new_col })
  end

  context.completion = nil
end

return M


================================================
FILE: lua/gemini/config.lua
================================================
local api = require('gemini.api')
local util = require('gemini.util')

local M = {}

local default_temperature = 0.06
local default_top_k = 64

local default_chat_config = {
  model = {
    model_id = api.MODELS.GEMINI_2_5_FLASH,
    temperature = default_temperature,
    top_k = default_top_k,
  },
  window = {
    position = "new_tab",     -- left, right, new_tab, tab
    width = 80,               -- number of columns of the left/right window
  }
}

local default_instruction_config = {
  model = {
    model_id = api.MODELS.GEMINI_2_5_FLASH,
    temperature = default_temperature,
    top_k = default_top_k,
  },
  menu_key = '<Leader><Leader><Leader>g',
  prompts = {
    {
      name = 'Unit Test',
      command_name = 'GeminiUnitTest',
      menu = 'Unit Test 🚀',
      get_prompt = function(lines, bufnr)
        local code = vim.fn.join(lines, '\n')
        local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
        local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
            .. 'Objective: Write unit test for the above snippet of code\n'
        return string.format(prompt, filetype, code)
      end,
    },
    {
      name = 'Code Review',
      command_name = 'GeminiCodeReview',
      menu = 'Code Review 📜',
      get_prompt = function(lines, bufnr)
        local code = vim.fn.join(lines, '\n')
        local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
        local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
            .. 'Objective: Do a thorough code review for the following code.\n'
            .. 'Provide detail explaination and sincere comments.\n'
        return string.format(prompt, filetype, code)
      end,
    },
    {
      name = 'Code Explain',
      command_name = 'GeminiCodeExplain',
      menu = 'Code Explain',
      get_prompt = function(lines, bufnr)
        local code = vim.fn.join(lines, '\n')
        local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
        local prompt = 'Context:\n\n```%s\n%s\n```\n\n'
            .. 'Objective: Explain the following code.\n'
            .. 'Provide detail explaination and sincere comments.\n'
        return string.format(prompt, filetype, code)
      end,
    },
  }
}

local default_completion_config = {
  model = {
    model_id = api.MODELS.GEMINI_2_5_FLASH,
    temperature = default_temperature,
    top_k = default_top_k,
  },
  blacklist_filetypes = { 'help', 'qf', 'json', 'yaml', 'toml', 'xml', 'ini' },
  blacklist_filenames = { '.env' },
  completion_delay = 800,
  insert_result_key = '<S-Tab>',
  move_cursor_end = true,
  can_complete = function()
    return vim.fn.pumvisible() ~= 1
  end,
  get_system_text = function()
    return "I need you to act as a pure code-completion tool."
  end,
  get_prompt = function(bufnr, pos)
    local abs_path = vim.api.nvim_buf_get_name(bufnr)
    local filename = vim.fn.fnamemodify(abs_path, ':.')
    local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })

    local radius = 10
    local row = pos[1]
    local col = pos[2]

    local start_line = math.max(1, row - radius)
    local line_count = vim.api.nvim_buf_line_count(bufnr)
    local end_line = math.min(line_count, row + radius)
    local lines = vim.api.nvim_buf_get_lines(bufnr, start_line, end_line, false)

    local prompt = 'I have a %s file %s. Below is a snippet around the missing code block.\n'
    prompt = string.format(prompt, filetype, filename)
    prompt = prompt .. '[CODE BEFORE GAP]\n'

    if start_line > 1 then
      prompt = prompt .. "// ... (earlier code omitted)\n"
    end

    for i, line in ipairs(lines) do
      local current_buffer_row = start_line + i - 1
      if current_buffer_row == row then
        local line_prefix = line:sub(1, col)
        local line_suffix = line:sub(col + 1)
        prompt = prompt .. line_prefix .. '\n\n[CODE AFTER GAP]\n' .. line_suffix .. '\n'
      else
        prompt = prompt .. line .. '\n'
      end
    end

    if end_line < line_count then
      prompt = prompt .. "// ... (later code omitted)\n"
    end

    prompt = prompt .. '\n[INSTRUCTION]\n'
    prompt = prompt .. 'Generate EXACTLY the code that fills the gap between the two blocks. '
    prompt = prompt .. 'Return ONLY the code. No explanation. Stop immediately when the logic is complete.\n'
    return prompt
  end
}

local default_task_config = {
  model = {
    model_id = api.MODELS.GEMINI_2_5_FLASH,
    temperature = default_temperature,
    top_k = default_top_k,
  },
  get_system_text = function()
    return 'You are an AI assistant that helps user write code.'
      .. '\n* You should output the new content for the Current Opened File'
  end,
  get_prompt = function(bufnr, user_prompt)
    local buffers = vim.api.nvim_list_bufs()
    local file_contents = {}

    for _, b in ipairs(buffers) do
      if vim.api.nvim_buf_is_loaded(b) then -- Only get content from loaded buffers
        local lines = vim.api.nvim_buf_get_lines(b, 0, -1, false)
        local abs_path = vim.api.nvim_buf_get_name(b)
        local filename = vim.fn.fnamemodify(abs_path, ':.')
        local filetype = vim.api.nvim_get_option_value('filetype', { buf = b })
        local file_content = table.concat(lines, "\n")
        file_content = string.format("`%s`:\n\n```%s\n%s\n```\n\n", filename, filetype, file_content)
        table.insert(file_contents, file_content)
      end
    end

    local current_filepath = vim.api.nvim_buf_get_name(bufnr)
    current_filepath = vim.fn.fnamemodify(current_filepath, ":.")

    local context = table.concat(file_contents, "\n\n")
    return string.format('%s\n\nCurrent Opened File: %s\n\nTask: %s',
      context, current_filepath, user_prompt)
  end
}

M.set_config = function(opts)
  opts = opts or {}

  M.config = {
    chat = vim.tbl_deep_extend('force', {}, default_chat_config, opts.chat_config or {}),
    completion = vim.tbl_deep_extend('force', {}, default_completion_config, opts.completion or {}),
    instruction = vim.tbl_deep_extend('force', {}, default_instruction_config, opts.instruction or {}),
    task = vim.tbl_deep_extend('force', {}, default_task_config, opts.task or {})
  }
end

M.get_config = function(keys)
  return util.table_get(M.config, keys)
end

M.get_gemini_generation_config = function(space)
  return {
    temperature = M.get_config({ space, 'model', 'temperature' }) or default_temperature,
    topK = M.get_config({ space, 'model', 'top_k' }) or default_top_k,
    response_mime_type = 'text/plain',
    thinkingConfig = {
      thinkingBudget = 0
    }
  }
end

return M


================================================
FILE: lua/gemini/instruction.lua
================================================
local config = require('gemini.config')
local util = require('gemini.util')
local api = require('gemini.api')

local M = {}

M.setup = function()
  local model = config.get_config({ 'instruction', 'model' })
  if not model or not model.model_id then
    return
  end

  local register_menu = function(prompt_item)
    local get_prompt = prompt_item.get_prompt
    if not get_prompt then
      return
    end

    local command_name = prompt_item.command_name
    if not command_name then
      return
    end

    local gemini_generate = function(context)
      local bufnr = vim.api.nvim_get_current_buf()
      local lines
      if not context.line1 or not context.line2 or context.line1 == context.line2 then
        lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
      else
        lines = vim.api.nvim_buf_get_lines(bufnr, context.line1 - 1, context.line2 - 1, false)
      end
      local user_text = get_prompt(lines, bufnr)

      local generation_config = config.get_gemini_generation_config('instruction')

      vim.api.nvim_command('tabnew')
      local new_buf = vim.api.nvim_get_current_buf()
      vim.api.nvim_set_option_value('filetype', 'markdown', { buf = new_buf })
      local model_id = config.get_config({ 'instruction', 'model', 'model_id' })
      local text = ''
      api.gemini_generate_content_stream(user_text, model_id, generation_config, function(json_text)
        local model_response = vim.json.decode(json_text)
        model_response = util.table_get(model_response, { 'candidates', 1, 'content', 'parts', 1, 'text' })
        text = text .. model_response
        vim.schedule(function()
          lines = vim.split(text, '\n')
          vim.api.nvim_buf_set_lines(new_buf, 0, -1, false, lines)
        end)
      end)
    end

    vim.api.nvim_create_user_command(command_name, gemini_generate, {
      range = true,
    })

    local menu = prompt_item.menu
    if menu then
      menu = menu:gsub(' ', '\\ ')
      vim.api.nvim_command('nnoremenu Gemini.' .. menu .. ' :' .. command_name .. '<CR>')
    end
  end

  for _, item in pairs(config.get_config({ 'instruction', 'prompts' }) or {}) do
    register_menu(item)
  end

  local register_keymap = function(mode, keymap)
    vim.api.nvim_set_keymap(mode, keymap, '', {
      expr = true,
      noremap = true,
      silent = true,
      callback = function()
        if vim.fn.pumvisible() == 0 then
          vim.api.nvim_command('popup Gemini')
        end
      end
    })
  end

  local modes = { 'n' }
  for _, mode in pairs(modes) do
    register_keymap(mode, config.get_config({ 'instruction', 'menu_key' }) or '<Leader><Leader><Leader>g')
  end
end

return M


================================================
FILE: lua/gemini/task.lua
================================================
local config = require('gemini.config')
local api = require('gemini.api')
local util = require('gemini.util')

local M = {}

local context = {
  bufnr = nil,
  model_response = nil,
  tmpfile = nil,
}

M.setup = function()
  local model = config.get_config({ 'task', 'model' })
  if not model or not model.model_id then
    return
  end

  vim.api.nvim_create_user_command('GeminiTask', M.run_task, {
    force = true,
    desc = 'Google Gemini',
    nargs = 1,
  })

  vim.api.nvim_create_user_command('GeminiApply', M.apply_patch, {
    force = true,
    desc = 'Apply patch',
  })
end

local get_prompt_text = function(bufnr, user_prompt)
  local get_prompt = config.get_config({ 'task', 'get_prompt' })
  if not get_prompt then
    vim.notify('prompt function is not found', vim.log.levels.WARN)
    return nil
  end
  return get_prompt(bufnr, user_prompt)
end

local function open_file_in_split(filepath, ft)
  local bufnr = vim.fn.bufnr(filepath, true)
  if bufnr == 0 then
    print("Error: Could not find or create buffer for file: " .. filepath)
    return
  end
  vim.api.nvim_set_option_value('filetype', ft, {buf = bufnr})

  local win_id = vim.api.nvim_open_win(bufnr, false, {
    split = 'right',
    win = 0,
  })
  vim.api.nvim_set_current_win(win_id)

  vim.api.nvim_set_option_value('diff', true, { win = win_id })
  vim.api.nvim_set_option_value('scrollbind', true, { win = win_id })
  vim.api.nvim_set_option_value('cursorbind', true, { win = win_id })
end

local function diff_with_current_file(bufnr, new_content)
  local tmpfile = vim.fn.tempname()

  -- Write to the temp file
  local f = io.open(tmpfile, "w")
  if f then
    f:write(new_content)
    f:close()
  end


  local ft = vim.api.nvim_get_option_value('filetype', {buf = bufnr})
  open_file_in_split(vim.fn.fnameescape(tmpfile), ft)
  return tmpfile
end

M.run_task = function(ctx)
  local bufnr = vim.api.nvim_get_current_buf()
  local user_prompt = ctx.args
  local prompt = get_prompt_text(bufnr, user_prompt)

  local system_text = nil
  local get_system_text = config.get_config({ 'task', 'get_system_text' })
  if get_system_text then
    system_text = get_system_text()
  end

  print('-- running Gemini Task...')
  local generation_config = config.get_gemini_generation_config('task')
  local model_id = config.get_config({ 'task', 'model', 'model_id' })
  api.gemini_generate_content(prompt, system_text, model_id, generation_config, function(result)
    local json_text = result.stdout
    if json_text and #json_text > 0 then
      local model_response = vim.json.decode(json_text)
      model_response = util.table_get(model_response, { 'candidates', 1, 'content', 'parts', 1, 'text' })
      if model_response ~= nil and #model_response > 0 then
        model_response = util.strip_code(model_response)
        vim.schedule(function()
          model_response = vim.fn.join(model_response, '\n')
          if #model_response then
            context.bufnr = bufnr
            context.model_response = model_response
            context.tmpfile = diff_with_current_file(bufnr, model_response)
          end
        end)
      end
    end
  end)
end

local function close_split_by_filename(tmpfile)
  -- Get the buffer number for the temp file
  local bufnr = vim.fn.bufnr(tmpfile)
  if bufnr == -1 then
    print("No buffer found for file: " .. tmpfile)
    return
  end

  -- Find the window displaying this buffer
  for _, win in ipairs(vim.api.nvim_list_wins()) do
    if vim.api.nvim_win_get_buf(win) == bufnr then
      vim.api.nvim_win_close(win, true)  -- force close the window
      vim.api.nvim_buf_delete(bufnr, { force = true, unload = true })
      return
    end
  end
  print("No window found showing the buffer for file: " .. tmpfile)
end

M.apply_patch = function()
  if not context.bufnr or not context.tmpfile then
    vim.notify('No Gemini task to apply.', vim.log.levels.WARN)
    return
  end

  print('-- apply changes from Gemini')

  local tmp_bufnr = vim.fn.bufnr(vim.fn.fnamemodify(context.tmpfile, ':p'))
  if tmp_bufnr == -1 then
    vim.notify('Could not find the temporary buffer for edited changes.', vim.log.levels.ERROR)
    return
  end

  local edited_lines = vim.api.nvim_buf_get_lines(tmp_bufnr, 0, -1, false)

  vim.api.nvim_buf_set_lines(context.bufnr, 0, -1, false, edited_lines)

  if context.tmpfile then
    close_split_by_filename(context.tmpfile)
  end

  context.bufnr = nil
  context.model_response = nil
  context.tmpfile = nil
end

return M


================================================
FILE: lua/gemini/util.lua
================================================
local M = {}

M.borderchars = { '─', '│', '─', '│', '╭', '╮', '╯', '╰' }

M.open_window = function(content, options)
  local popup = require('plenary.popup')
  options.borderchars = M.borderchars
  local win_id, result = popup.create(content, options)
  local bufnr = vim.api.nvim_win_get_buf(win_id)
  local border = result.border
  vim.api.nvim_set_option_value('ft', 'markdown', { buf = bufnr })
  vim.api.nvim_set_option_value('wrap', true, { win = win_id })

  local close_popup = function()
    vim.api.nvim_win_close(win_id, true)
  end

  local keys = { '<C-q>', 'q' }
  for _, key in pairs(keys) do
    vim.api.nvim_buf_set_keymap(bufnr, 'n', key, '', {
      silent = true,
      callback = close_popup,
    })
  end
  return win_id, bufnr, border
end

M.treesitter_has_lang = function(bufnr)
  local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
  local lang = vim.treesitter.language.get_lang(filetype)
  return lang ~= nil
end

M.find_node_by_type = function(node_type)
  local node = vim.treesitter.get_node()
  while node do
    local type = node:type()
    if string.find(type, node_type) then
      return node
    end

    local parent = node:parent()
    if parent == node then
      break
    end
    node = parent
  end
  return nil
end

M.debounce = function(callback, timeout)
  local timer = nil
  local f = function(...)
    local t = { ... }
    local handler = function()
      callback(unpack(t))
    end

    if timer ~= nil then
      timer:stop()
    end
    timer = vim.defer_fn(handler, timeout)
  end
  return f
end

M.table_get = function(t, id)
  if type(id) ~= 'table' then return M.table_get(t, { id }) end
  local success, res = true, t
  for _, i in ipairs(id) do
    success, res = pcall(function() return res[i] end)
    if not success or res == nil then return end
  end
  return res
end

M.is_blacklisted = function(blacklist, filetype)
  for _, ft in ipairs(blacklist) do
    if string.find(filetype, ft, 1, true) ~= nil then
      return true
    end
  end
  return false
end

M.strip_code = function(text)
  local code_blocks = {}
  if not text then
    return code_blocks
  end

  local pattern = "```(%w+)%s*(.-)%s*```"
  for _, code_block in text:gmatch(pattern) do
    table.insert(code_blocks, code_block)
  end
  if #code_blocks == 0 then
    return { text }
  end
  return code_blocks
end

return M


================================================
FILE: lua/gemini.lua
================================================
local config = require('gemini.config')

local M = {}

local function is_nvim_version_ge(major, minor, patch)
  local v = vim.version()
  if v.major > major then
    return true
  elseif v.major == major then
    if v.minor > minor then
      return true
    elseif v.minor == minor and v.patch >= patch then
      return true
    end
  end
  return false
end

M.setup = function(opts)
  if not vim.fn.executable('curl') then
    vim.notify('curl is not found', vim.log.levels.WARN)
    return
  end

  if not is_nvim_version_ge(0, 10, 0) then
    vim.notify('neovim version too old', vim.log.levels.WARN)
    return
  end

  config.set_config(opts)

  require('gemini.chat').setup()
  require('gemini.instruction').setup()
  require('gemini.completion').setup()
  require('gemini.task').setup()
end

return M


================================================
FILE: tests/gemini/api_spec.lua
================================================
local api = require('gemini.api')
local util = require('gemini.util')

describe('api', function()
  it('should send message', function()
    local generation_config = {
      temperature = 0.9,
      top_k = 1.0,
      max_output_tokens = 2048,
      response_mime_type = 'text/plain',
    }
    local future = api.gemini_generate_content('hello there', nil, api.MODELS.GEMINI_2_0_FLASH, generation_config, nil)
    local result = future:wait()
    local stdout = result.stdout
    print(stdout)
    assert(#stdout > 0)

    local result = vim.json.decode(stdout)
    local model_response = util.table_get(result, { 'candidates', 1, 'content',
      'parts', 1, 'text' })
    assert(#model_response > 0)
  end)

  it('should send long message', function()
    local generation_config = {
      temperature = 0.9,
      top_k = 1.0,
      max_output_tokens = 2048,
      response_mime_type = 'text/plain',
    }
    local long_message = string.rep('this is a very very long message ', 3000)
    local future = api.gemini_generate_content(long_message, nil, api.MODELS.GEMINI_2_0_FLASH, generation_config, nil)
    local result = future:wait()
    local stdout = result.stdout
    print(stdout)
    assert(#stdout > 0)

    local result = vim.json.decode(stdout)
    local model_response = util.table_get(result, { 'candidates', 1, 'content',
      'parts', 1, 'text' })
    assert(#model_response > 0)
  end)
end)


================================================
FILE: tests/init.lua
================================================
local plenary_dir = os.getenv("PLENARY_DIR") or "/tmp/plenary.nvim"
local is_not_a_directory = vim.fn.isdirectory(plenary_dir) == 0
if is_not_a_directory then
  vim.fn.system({"git", "clone", "https://github.com/nvim-lua/plenary.nvim", plenary_dir})
end

vim.opt.rtp:append(".")
vim.opt.rtp:append(plenary_dir)

vim.cmd("runtime plugin/plenary.vim")
require("plenary.busted")
Download .txt
gitextract_p6arm2ii/

├── .gitignore
├── .luacheckrs
├── LICENSE
├── Makefile
├── README.md
├── doc/
│   └── gemini.nvim.txt
├── lua/
│   ├── gemini/
│   │   ├── api.lua
│   │   ├── chat.lua
│   │   ├── completion.lua
│   │   ├── config.lua
│   │   ├── instruction.lua
│   │   ├── task.lua
│   │   └── util.lua
│   └── gemini.lua
└── tests/
    ├── gemini/
    │   └── api_spec.lua
    └── init.lua
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (48K chars).
[
  {
    "path": ".gitignore",
    "chars": 57,
    "preview": "__pycache__\n*.db\n\nlua/gemini/*.so\n.cache\nbuild\n\ndoc/tags\n"
  },
  {
    "path": ".luacheckrs",
    "chars": 25,
    "preview": "read_globals = { \"vim\" }\n"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2025 Joseph\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "Makefile",
    "chars": 300,
    "preview": "TESTS_INIT=tests/init.lua\nTESTS_DIR=tests/\n\nall:\n\tcmake -B build\n\tmake -C build -j\n\nclean:\n\trm -rf build\n\trm -rf lua/gem"
  },
  {
    "path": "README.md",
    "chars": 6704,
    "preview": "# gemini.nvim\n\nThis plugin try to interface Google's Gemini API into neovim.\n\n\n## Features\n\n- Code Complete\n- Code Expla"
  },
  {
    "path": "doc/gemini.nvim.txt",
    "chars": 6563,
    "preview": "*gemini.nvim*    Google's Gemini neovim binding\n\nINTRODUCTION                                    *gemini.nvim-intro*\n\nTh"
  },
  {
    "path": "lua/gemini/api.lua",
    "chars": 3152,
    "preview": "local uv = vim.loop or vim.uv\n\nlocal M = {}\n\nlocal API = \"https://generativelanguage.googleapis.com/v1beta/models/\";\n\nM."
  },
  {
    "path": "lua/gemini/chat.lua",
    "chars": 2692,
    "preview": "local config = require('gemini.config')\nlocal util = require('gemini.util')\nlocal api = require('gemini.api')\n\nlocal M ="
  },
  {
    "path": "lua/gemini/completion.lua",
    "chars": 5955,
    "preview": "local config = require('gemini.config')\nlocal util = require('gemini.util')\nlocal api = require('gemini.api')\n\nlocal M ="
  },
  {
    "path": "lua/gemini/config.lua",
    "chars": 6593,
    "preview": "local api = require('gemini.api')\nlocal util = require('gemini.util')\n\nlocal M = {}\n\nlocal default_temperature = 0.06\nlo"
  },
  {
    "path": "lua/gemini/instruction.lua",
    "chars": 2671,
    "preview": "local config = require('gemini.config')\nlocal util = require('gemini.util')\nlocal api = require('gemini.api')\n\nlocal M ="
  },
  {
    "path": "lua/gemini/task.lua",
    "chars": 4489,
    "preview": "local config = require('gemini.config')\nlocal api = require('gemini.api')\nlocal util = require('gemini.util')\n\nlocal M ="
  },
  {
    "path": "lua/gemini/util.lua",
    "chars": 2377,
    "preview": "local M = {}\n\nM.borderchars = { '─', '│', '─', '│', '╭', '╮', '╯', '╰' }\n\nM.open_window = function(content, options)\n  l"
  },
  {
    "path": "lua/gemini.lua",
    "chars": 810,
    "preview": "local config = require('gemini.config')\n\nlocal M = {}\n\nlocal function is_nvim_version_ge(major, minor, patch)\n  local v "
  },
  {
    "path": "tests/gemini/api_spec.lua",
    "chars": 1413,
    "preview": "local api = require('gemini.api')\nlocal util = require('gemini.util')\n\ndescribe('api', function()\n  it('should send mess"
  },
  {
    "path": "tests/init.lua",
    "chars": 376,
    "preview": "local plenary_dir = os.getenv(\"PLENARY_DIR\") or \"/tmp/plenary.nvim\"\nlocal is_not_a_directory = vim.fn.isdirectory(plenar"
  }
]

About this extraction

This page contains the full source code of the kiddos/gemini.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (44.2 KB), approximately 12.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!