10 Commits

Author SHA1 Message Date
9c99944f26 updating files 2026-01-11 20:12:13 -05:00
29f321995d fix: improve code prompts to output only raw code
Add explicit instruction to all code generation prompts to return
only raw code without explanations, markdown, or code fences.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:48:17 -05:00
9d52b9364f updating files 2026-01-11 17:58:56 -05:00
86504eec55 updating files 2026-01-11 17:38:25 -05:00
d75196b4f9 updating code malformed 2026-01-11 17:14:14 -05:00
5b90a8e5f9 fix: make transform keymaps work in both normal and visual modes
Fixed keymaps to use command strings instead of function references:
- <leader>ctt (visual): Uses :<C-u>CoderTransformVisual<CR>
- <leader>ctt (normal): Uses <cmd>CoderTransformCursor<CR>
- <leader>ctT (normal): Uses <cmd>CoderTransform<CR>

Also fixed CoderTransformVisual to properly receive line range from opts.
2026-01-11 15:42:34 -05:00
bbdc1aa849 feat: add visual mode transform with <leader>ctt keymap
New keymaps for inline /@ @/ tag transformation:
- <leader>ctt (visual): Transform tags within selection
- <leader>ctt (normal): Transform tag at cursor
- <leader>ctT (normal): Transform ALL tags in file

New command:
- :CoderTransformVisual - Transform tags in visual selection

Usage:
1. Select lines containing /@ @/ tags
2. Press <leader>ctt
3. Selected tags are replaced with generated code
2026-01-11 15:41:42 -05:00
d11a99ee61 feat: add :CoderTransform command for inline tag replacement
New feature: Transform /@ @/ tags directly in any file, not just
.coder.* files. The tags are replaced inline with LLM-generated code.

New commands:
- :CoderTransform - Transform ALL /@ @/ tags in current file
- :CoderTransformCursor - Transform only the tag at cursor position
- :Coder transform - Same as :CoderTransform
- :Coder transform-cursor - Same as :CoderTransformCursor

How it works:
1. Write /@ your prompt @/ anywhere in your code
2. Run :CoderTransform
3. The tag is replaced with generated code matching your file's style

Example:
  /@ create a function to validate email @/

  Becomes:
  function validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
2026-01-11 15:38:41 -05:00
8130a78ed5 fix: make prompts language-agnostic, support all file types
The plugin now properly detects and uses the file's language based on
its extension, rather than focusing on TypeScript.

Changes:
- Updated all system prompts to use {{language}} placeholder
- Removed hardcoded TypeScript references
- Expanded language mappings to support 50+ file extensions:
  - JavaScript/TypeScript (js, ts, jsx, tsx, mjs, cjs)
  - Python (py, pyw, pyx)
  - Systems (c, cpp, rs, go, zig, nim)
  - JVM (java, kt, scala, clj)
  - Web (html, css, scss, vue, svelte)
  - Scripting (lua, rb, php, sh, bash)
  - .NET (cs, fs, vb)
  - Data (json, yaml, toml, sql, graphql)
  - And many more...
2026-01-11 15:33:28 -05:00
fe04e624db fix: ensure LLM outputs only code, not plain text
Improved prompts and code extraction to ensure generated code:
- Matches the file type and existing code structure
- Contains NO markdown code blocks (```)
- Contains NO explanations or comments about what was done
- Is ready to be directly inserted into the target file

Changes:
- Enhanced system prompts with stricter "ABSOLUTE RULES"
- Added explicit instructions about no markdown in user prompts
- Improved extract_code() to strip markdown and explanations
- Added processing notification with prompt preview
2026-01-11 15:31:23 -05:00
23 changed files with 2289 additions and 1060 deletions

95
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: 🐛 Bug Report
description: Create a bug report to help us improve Avante
title: 'bug: '
labels: ['bug']
body:
- type: markdown
id: issue-already-exists
attributes:
value: |
Please search to see if an issue already exists for the bug you encountered.
See [Searching Issues and Pull Requests](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests) for how to use the GitHub search bar and filters.
- type: textarea
id: describe-the-bug
validations:
required: true
attributes:
label: Describe the bug
description: Please provide a clear and concise description about the problem you ran into.
placeholder: This happened when I ...
- type: textarea
id: to-reproduce
validations:
required: false
attributes:
label: To reproduce
description: |
Please provide a code sample or a code snippet to reproduce said problem. If you have code snippets, error messages, or a stack trace please also provide them here.
**IMPORTANT**: make sure to use [code tags](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) to correctly format your code. Screenshots are helpful but don't use them for code snippets as they don't allow others to copy-and-paste your code.
placeholder: |
Give a minimal config to reproduce the issue.
- type: textarea
id: expected-behavior
validations:
required: false
attributes:
label: Expected behavior
description: 'A clear and concise description of what you would expect to happen.'
- type: textarea
id: how-to-install
validations:
required: true
attributes:
label: Installation method
description: |
Please share your installation method with us.
value: |
Use lazy.nvim:
```lua
{
"yetone/avante.nvim",
event = "VeryLazy",
lazy = false,
version = false, -- set this if you want to always pull the latest change
opts = {
-- add any opts here
},
-- if you want to build from source then do `make BUILD_FROM_SOURCE=true`
build = "make",
-- build = "powershell -ExecutionPolicy Bypass -File Build.ps1 -BuildFromSource false" -- for windows
dependencies = {
"nvim-lua/plenary.nvim",
"MunifTanjim/nui.nvim",
},
}
```
- type: textarea
id: environment-info
attributes:
label: Environment
description: |
Please share your environment with us, including your neovim version using `nvim -v` and `uname -a`.
placeholder: |
neovim version: ...
distribution (if any): ...
platform: ...
validations:
required: true
- type: textarea
attributes:
label: Repro
description: Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua`
value: |
vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
require("lazy.minit").repro({
spec = {
-- add any other plugins here
},
})
render: lua
validations:
required: false

2
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
blank_issues_enabled: true
version: 2.1

View File

@@ -0,0 +1,35 @@
name: 🚀 Feature Request
description: Submit a proposal/request for new Avante feature.
title: 'feature: '
labels: ['new-feature', 'enhancement']
body:
- type: textarea
id: feature-request
validations:
required: true
attributes:
label: Feature request
description: |
A clear and concise description of the feature request.
placeholder: |
I would like it if...
- type: textarea
id: motivation
validations:
required: false
attributes:
label: Motivation
description: |
Please outline the motivation for this feature request. Is your feature request related to a problem? e.g., I'm always frustrated when [...].
If this is related to another issue, please link here too.
If you have a current workaround, please also provide it here.
placeholder: |
This feature would solve ...
- type: textarea
id: other
attributes:
label: Other
description: |
Is there any way that you could help, e.g. by submitting a PR?
placeholder: |
I would love to contribute ...

View File

@@ -0,0 +1,24 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
permissions:
contents: write # only for delete-branch option
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
days-before-issue-stale: 30
days-before-pr-stale: 14
days-before-issue-close: 5
days-before-pr-close: 10

86
.github/workflows/lua.yaml vendored Normal file
View File

@@ -0,0 +1,86 @@
name: Lua CI
on:
push:
branches:
- main
paths:
- "**/*.lua"
- .github/workflows/lua.yaml
pull_request:
branches:
- main
paths:
- "**/*.lua"
- .github/workflows/lua.yaml
jobs:
# reference from: https://github.com/nvim-lua/plenary.nvim/blob/2d9b06177a975543726ce5c73fca176cedbffe9d/.github/workflows/default.yml#L6C3-L43C20
run_tests:
name: unit tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
rev: v0.10.0
steps:
- uses: actions/checkout@v6
- id: todays-date
run: echo "date=$(date +%F)" >> "$GITHUB_OUTPUT"
- name: Restore cache for today's nightly.
id: cache-neovim
uses: actions/cache@v4
with:
path: _neovim
key: ${{ runner.os }}-${{ matrix.rev }}-${{ steps.todays-date.outputs.date }}
- name: Download neovim ${{ matrix.rev }}
env:
GH_TOKEN: ${{ github.token }}
NEOVIM_VERSION: ${{ matrix.rev }}
if: steps.cache-neovim.outputs.cache-hit != 'true'
run: |
mkdir -p _neovim
gh release download \
--output - \
--pattern nvim-linux64.tar.gz \
--repo neovim/neovim \
"$NEOVIM_VERSION" | tar xvz --strip-components 1 --directory _neovim
- name: Prepare
run: |
sudo apt-get update
sudo apt-get install -y ripgrep
sudo apt-get install -y silversearcher-ag
echo "${PWD}/_neovim/bin" >> "$GITHUB_PATH"
echo VIM="${PWD}/_neovim/share/nvim/runtime" >> "$GITHUB_ENV"
- name: Run tests
run: |
nvim --version
make luatest
typecheck:
name: Typecheck
runs-on: ubuntu-latest
strategy:
matrix:
nvim_version: [ stable ]
luals_version: [ 3.13.6 ]
steps:
- uses: actions/checkout@v6
- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.nvim_version }}
- name: Typecheck
env:
VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime
run: |
make lua-typecheck

38
.github/workflows/pre-commit.yaml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: pre-commit
on:
pull_request:
types: [labeled, opened, reopened, synchronize]
push:
branches: [main, test-me-*]
jobs:
pre-commit:
if: "github.event.action != 'labeled' || github.event.label.name == 'pre-commit ci run'"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: gh pr edit ${{ github.event.number }} --remove-label 'pre-commit ci run'
if: github.event.action == 'labeled' && github.event.label.name == 'pre-commit ci run'
env:
GH_TOKEN: ${{ github.token }}
- uses: actions/setup-python@v3
with:
python-version: '3.11'
- name: Install uv
uses: astral-sh/setup-uv@v5
- run: |
uv venv
source .venv/bin/activate
uv pip install -r py/rag-service/requirements.txt
- uses: leafo/gh-actions-lua@v11
- uses: leafo/gh-actions-luarocks@v5
- run: luarocks install luacheck
- name: Install stylua from crates.io
uses: baptiste0928/cargo-install@v3
with:
crate: stylua
args: --features lua54
- uses: pre-commit/action@v3.0.1
- uses: pre-commit-ci/lite-action@v1.1.0
if: always()

185
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,185 @@
name: Release
on:
push:
tags: [v\d+\.\d+\.\d+]
permissions:
contents: write
packages: write
env:
CARGO_TERM_COLOR: always
jobs:
create-release:
permissions:
contents: write
runs-on: ubuntu-24.04
outputs:
release_id: ${{ steps.create-release.outputs.id }}
release_upload_url: ${{ steps.create-release.outputs.upload_url }}
release_body: "${{ steps.tag.outputs.message }}"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4
- name: Get version
id: get_version
uses: battila7/get-version-action@d97fbc34ceb64d1f5d95f4dfd6dce33521ccccf5 # ratchet:battila7/get-version-action@v2
- name: Get tag message
id: tag
run: |
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
echo "message<<EOF" >> $GITHUB_OUTPUT
echo "$(git tag -l --format='%(contents)' ${{ steps.get_version.outputs.version }})" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
id: create-release
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # ratchet:ncipollo/release-action@v1
with:
draft: true
name: "avante-libs ${{ steps.get_version.outputs.version }}"
tag: ${{ steps.get_version.outputs.version }}
body: "${{ steps.tag.outputs.message }}"
releases-matrix:
needs: [create-release]
strategy:
fail-fast: false
matrix:
feature: [lua51, luajit]
config:
- os: ubuntu-24.04-arm
os_name: linux
arch: aarch64
rust_target: aarch64-unknown-linux-gnu
docker_platform: linux/aarch64
container: quay.io/pypa/manylinux2014_aarch64
- os: ubuntu-latest
os_name: linux
arch: x86_64
rust_target: x86_64-unknown-linux-gnu
docker_platform: linux/amd64
container: quay.io/pypa/manylinux2014_x86_64 # for glibc 2.17
- os: macos-13
os_name: darwin
arch: x86_64
rust_target: x86_64-apple-darwin
- os: macos-latest
os_name: darwin
arch: aarch64
rust_target: aarch64-apple-darwin
- os: windows-latest
os_name: windows
arch: x86_64
rust_target: x86_64-pc-windows-msvc
- os: windows-latest
os_name: windows
arch: aarch64
rust_target: aarch64-pc-windows-msvc
runs-on: ${{ matrix.config.os }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4
- uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # ratchet:Swatinem/rust-cache@v2
if: ${{ matrix.config.container == null }}
- uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # ratchet:dtolnay/rust-toolchain@master
if: ${{ matrix.config.container == null }}
with:
targets: ${{ matrix.config.rust_target }}
toolchain: "1.85.0"
- name: Build all crates
if: ${{ matrix.config.container == null }}
run: |
cargo build --release --features ${{ matrix.feature }}
- name: Build all crates with glibc 2.17 # for glibc 2.17
if: ${{ matrix.config.container != null }}
run: |
# sudo apt-get install -y qemu qemu-user-static
docker run \
--rm \
-v $(pwd):/workspace \
-w /workspace \
--platform ${{ matrix.config.docker_platform }} \
${{ matrix.config.container }} \
bash -c "yum install -y perl-IPC-Cmd openssl-devel && curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal && . /root/.cargo/env && cargo build --release --features ${{ matrix.feature }}"
- name: Handle binaries
if: ${{ matrix.config.os_name != 'windows' }}
shell: bash
run: |
mkdir -p results
if [ "${{ matrix.config.os_name }}" == "linux" ]; then
EXT="so"
else
EXT="dylib"
fi
cp target/release/libavante_templates.$EXT results/avante_templates.$EXT
cp target/release/libavante_tokenizers.$EXT results/avante_tokenizers.$EXT
cp target/release/libavante_repo_map.$EXT results/avante_repo_map.$EXT
cp target/release/libavante_html2md.$EXT results/avante_html2md.$EXT
cd results
tar zcvf avante_lib-${{ matrix.config.os_name }}-${{ matrix.config.arch }}-${{ matrix.feature }}.tar.gz *.${EXT}
- name: Handle binaries (Windows)
if: ${{ matrix.config.os_name == 'windows' }}
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path results
Copy-Item -Path "target\release\avante_templates.dll" -Destination "results\avante_templates.dll"
Copy-Item -Path "target\release\avante_tokenizers.dll" -Destination "results\avante_tokenizers.dll"
Copy-Item -Path "target\release\avante_repo_map.dll" -Destination "results\avante_repo_map.dll"
Copy-Item -Path "target\release\avante_html2md.dll" -Destination "results\avante_html2md.dll"
Set-Location -Path results
$dllFiles = Get-ChildItem -Filter "*.dll" | Select-Object -ExpandProperty Name
Compress-Archive -Path $dllFiles -DestinationPath "avante_lib-${{ matrix.config.os_name }}-${{ matrix.config.arch }}-${{ matrix.feature }}.zip"
- name: Upload Release Asset
uses: shogo82148/actions-upload-release-asset@8482bd769644976d847e96fb4b9354228885e7b4 # ratchet:shogo82148/actions-upload-release-asset@v1
if: ${{ matrix.config.os_name != 'windows' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSET_NAME: avante_lib-${{ matrix.config.os_name }}-${{ matrix.config.arch }}-${{ matrix.feature }}.tar.gz
with:
upload_url: ${{ needs.create-release.outputs.release_upload_url }}
asset_path: ./results/avante_lib-${{ matrix.config.os_name }}-${{ matrix.config.arch }}-${{ matrix.feature }}.tar.gz
- name: Upload Release Asset (Windows)
uses: shogo82148/actions-upload-release-asset@8482bd769644976d847e96fb4b9354228885e7b4 # ratchet:shogo82148/actions-upload-release-asset@v1
if: ${{ matrix.config.os_name == 'windows' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSET_NAME: avante_lib-${{ matrix.config.os_name }}-${{ matrix.config.arch }}-${{ matrix.feature }}.zip
with:
upload_url: ${{ needs.create-release.outputs.release_upload_url }}
asset_path: ./results/avante_lib-${{ matrix.config.os_name }}-${{ matrix.config.arch }}-${{ matrix.feature }}.zip
publish-release:
permissions:
contents: write
runs-on: ubuntu-24.04
needs: [create-release, releases-matrix]
steps:
- name: publish release
id: publish-release
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # ratchet:actions/github-script@v6
env:
release_id: ${{ needs.create-release.outputs.release_id }}
with:
script: |
github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: process.env.release_id,
draft: false,
prerelease: false
})

31
.github/workflows/rust.yaml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Rust CI
on:
push:
branches:
- main
paths:
- "crates/**/*"
- "Cargo.lock"
- "Cargo.toml"
pull_request:
branches:
- main
paths:
- "crates/**/*"
- "Cargo.lock"
- "Cargo.toml"
jobs:
tests:
name: Run Rust tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4
- uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # ratchet:Swatinem/rust-cache@v2
- uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # ratchet:dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy, rustfmt
- name: Run rust tests
run: cargo test --features luajit

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Codetyper.nvim - AI coding partner files
*.coder.*
.coder/
/@
add gitignore for lua files
/@

View File

@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- Improved code generation prompts to explicitly request only raw code output (no explanations, markdown, or code fences)
## [0.2.0] - 2026-01-11
### Added

View File

@@ -39,7 +39,7 @@ This project and everyone participating in it is governed by our commitment to c
1. Clone the repository:
```bash
git clone https://github.com/YOUR_USERNAME/codetyper.nvim.git
git clone https://github.com/CarGDev/codetyper.nvim.git
cd codetyper.nvim
```

File diff suppressed because it is too large Load Diff

View File

@@ -20,182 +20,198 @@ local processed_prompts = {}
---@param prompt table Prompt object
---@return string Unique key
local function get_prompt_key(bufnr, prompt)
return string.format("%d:%d:%d:%s", bufnr, prompt.start_line, prompt.end_line, prompt.content:sub(1, 50))
return string.format("%d:%d:%d:%s", bufnr, prompt.start_line, prompt.end_line, prompt.content:sub(1, 50))
end
--- Schedule tree update with debounce
local function schedule_tree_update()
if tree_update_timer then
tree_update_timer:stop()
end
if tree_update_timer then
tree_update_timer:stop()
end
tree_update_timer = vim.defer_fn(function()
local tree = require("codetyper.tree")
tree.update_tree_log()
tree_update_timer = nil
end, TREE_UPDATE_DEBOUNCE_MS)
tree_update_timer = vim.defer_fn(function()
local tree = require("codetyper.tree")
tree.update_tree_log()
tree_update_timer = nil
end, TREE_UPDATE_DEBOUNCE_MS)
end
--- Setup autocommands
function M.setup()
local group = vim.api.nvim_create_augroup(AUGROUP, { clear = true })
local group = vim.api.nvim_create_augroup(AUGROUP, { clear = true })
-- Auto-save coder file when leaving insert mode
vim.api.nvim_create_autocmd("InsertLeave", {
group = group,
pattern = "*.coder.*",
callback = function()
-- Auto-save the coder file
if vim.bo.modified then
vim.cmd("silent! write")
end
-- Check for closed prompts and auto-process
M.check_for_closed_prompt()
end,
desc = "Auto-save and check for closed prompt tags",
})
-- Auto-save coder file when leaving insert mode
vim.api.nvim_create_autocmd("InsertLeave", {
group = group,
pattern = "*.coder.*",
callback = function()
-- Auto-save the coder file
if vim.bo.modified then
vim.cmd("silent! write")
end
-- Check for closed prompts and auto-process
M.check_for_closed_prompt()
end,
desc = "Auto-save and check for closed prompt tags",
})
-- Auto-set filetype for coder files based on extension
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
group = group,
pattern = "*.coder.*",
callback = function()
M.set_coder_filetype()
end,
desc = "Set filetype for coder files",
})
-- Auto-set filetype for coder files based on extension
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
group = group,
pattern = "*.coder.*",
callback = function()
M.set_coder_filetype()
end,
desc = "Set filetype for coder files",
})
-- Auto-open split view when opening a coder file directly (e.g., from nvim-tree)
vim.api.nvim_create_autocmd("BufEnter", {
group = group,
pattern = "*.coder.*",
callback = function()
-- Delay slightly to ensure buffer is fully loaded
vim.defer_fn(function()
M.auto_open_target_file()
end, 50)
end,
desc = "Auto-open target file when coder file is opened",
})
-- Auto-open split view when opening a coder file directly (e.g., from nvim-tree)
vim.api.nvim_create_autocmd("BufEnter", {
group = group,
pattern = "*.coder.*",
callback = function()
-- Delay slightly to ensure buffer is fully loaded
vim.defer_fn(function()
M.auto_open_target_file()
end, 50)
end,
desc = "Auto-open target file when coder file is opened",
})
-- Cleanup on buffer close
vim.api.nvim_create_autocmd("BufWipeout", {
group = group,
pattern = "*.coder.*",
callback = function(ev)
local window = require("codetyper.window")
if window.is_open() then
window.close_split()
end
-- Clear processed prompts for this buffer
local bufnr = ev.buf
for key, _ in pairs(processed_prompts) do
if key:match("^" .. bufnr .. ":") then
processed_prompts[key] = nil
end
end
-- Clear auto-opened tracking
M.clear_auto_opened(bufnr)
end,
desc = "Cleanup on coder buffer close",
})
-- Cleanup on buffer close
vim.api.nvim_create_autocmd("BufWipeout", {
group = group,
pattern = "*.coder.*",
callback = function(ev)
local window = require("codetyper.window")
if window.is_open() then
window.close_split()
end
-- Clear processed prompts for this buffer
local bufnr = ev.buf
for key, _ in pairs(processed_prompts) do
if key:match("^" .. bufnr .. ":") then
processed_prompts[key] = nil
end
end
-- Clear auto-opened tracking
M.clear_auto_opened(bufnr)
end,
desc = "Cleanup on coder buffer close",
})
-- Update tree.log when files are created/written
vim.api.nvim_create_autocmd({ "BufWritePost", "BufNewFile" }, {
group = group,
pattern = "*",
callback = function(ev)
-- Skip coder files and tree.log itself
local filepath = ev.file or vim.fn.expand("%:p")
if filepath:match("%.coder%.") or filepath:match("tree%.log$") then
return
end
-- Schedule tree update with debounce
schedule_tree_update()
end,
desc = "Update tree.log on file creation/save",
})
-- Update tree.log when files are created/written
vim.api.nvim_create_autocmd({ "BufWritePost", "BufNewFile" }, {
group = group,
pattern = "*",
callback = function(ev)
-- Skip coder files and tree.log itself
local filepath = ev.file or vim.fn.expand("%:p")
if filepath:match("%.coder%.") or filepath:match("tree%.log$") then
return
end
-- Schedule tree update with debounce
schedule_tree_update()
end,
desc = "Update tree.log on file creation/save",
})
-- Update tree.log when files are deleted (via netrw or file explorer)
vim.api.nvim_create_autocmd("BufDelete", {
group = group,
pattern = "*",
callback = function(ev)
local filepath = ev.file or ""
-- Skip special buffers and coder files
if filepath == "" or filepath:match("%.coder%.") or filepath:match("tree%.log$") then
return
end
schedule_tree_update()
end,
desc = "Update tree.log on file deletion",
})
-- Update tree.log when files are deleted (via netrw or file explorer)
vim.api.nvim_create_autocmd("BufDelete", {
group = group,
pattern = "*",
callback = function(ev)
local filepath = ev.file or ""
-- Skip special buffers and coder files
if filepath == "" or filepath:match("%.coder%.") or filepath:match("tree%.log$") then
return
end
schedule_tree_update()
end,
desc = "Update tree.log on file deletion",
})
-- Update tree on directory change
vim.api.nvim_create_autocmd("DirChanged", {
group = group,
pattern = "*",
callback = function()
schedule_tree_update()
end,
desc = "Update tree.log on directory change",
})
-- Update tree on directory change
vim.api.nvim_create_autocmd("DirChanged", {
group = group,
pattern = "*",
callback = function()
schedule_tree_update()
end,
desc = "Update tree.log on directory change",
})
end
--- Get config with fallback defaults
local function get_config_safe()
local codetyper = require("codetyper")
local config = codetyper.get_config()
-- Return defaults if not initialized
if not config or not config.patterns then
return {
patterns = {
open_tag = "/@",
close_tag = "@/",
file_pattern = "*.coder.*",
}
}
end
return config
end
--- Check if the buffer has a newly closed prompt and auto-process
function M.check_for_closed_prompt()
local codetyper = require("codetyper")
local config = codetyper.get_config()
local parser = require("codetyper.parser")
local config = get_config_safe()
local parser = require("codetyper.parser")
local bufnr = vim.api.nvim_get_current_buf()
local bufnr = vim.api.nvim_get_current_buf()
-- Get current line
local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1]
local lines = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)
-- Get current line
local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1]
local lines = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)
if #lines == 0 then
return
end
if #lines == 0 then
return
end
local current_line = lines[1]
local current_line = lines[1]
-- Check if line contains closing tag
if parser.has_closing_tag(current_line, config.patterns.close_tag) then
-- Find the complete prompt
local prompt = parser.get_last_prompt(bufnr)
if prompt and prompt.content and prompt.content ~= "" then
-- Generate unique key for this prompt
local prompt_key = get_prompt_key(bufnr, prompt)
-- Check if line contains closing tag
if parser.has_closing_tag(current_line, config.patterns.close_tag) then
-- Find the complete prompt
local prompt = parser.get_last_prompt(bufnr)
if prompt and prompt.content and prompt.content ~= "" then
-- Generate unique key for this prompt
local prompt_key = get_prompt_key(bufnr, prompt)
-- Check if already processed
if processed_prompts[prompt_key] then
return
end
-- Check if already processed
if processed_prompts[prompt_key] then
return
end
-- Mark as processed
processed_prompts[prompt_key] = true
-- Mark as processed
processed_prompts[prompt_key] = true
-- Auto-process the prompt (no confirmation needed)
utils.notify("Processing prompt...", vim.log.levels.INFO)
vim.schedule(function()
vim.cmd("CoderProcess")
end)
end
end
-- Auto-process the prompt (no confirmation needed)
utils.notify("Processing prompt...", vim.log.levels.INFO)
vim.schedule(function()
vim.cmd("CoderProcess")
end)
end
end
end
--- Reset processed prompts for a buffer (useful for re-processing)
---@param bufnr? number Buffer number (default: current)
function M.reset_processed(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
for key, _ in pairs(processed_prompts) do
if key:match("^" .. bufnr .. ":") then
processed_prompts[key] = nil
end
end
utils.notify("Prompt history cleared - prompts can be re-processed")
bufnr = bufnr or vim.api.nvim_get_current_buf()
for key, _ in pairs(processed_prompts) do
if key:match("^" .. bufnr .. ":") then
processed_prompts[key] = nil
end
end
utils.notify("Prompt history cleared - prompts can be re-processed")
end
--- Track if we already opened the split for this buffer
@@ -204,146 +220,146 @@ local auto_opened_buffers = {}
--- Auto-open target file when a coder file is opened directly
function M.auto_open_target_file()
local window = require("codetyper.window")
local window = require("codetyper.window")
-- Skip if split is already open
if window.is_open() then
return
end
-- Skip if split is already open
if window.is_open() then
return
end
local bufnr = vim.api.nvim_get_current_buf()
local bufnr = vim.api.nvim_get_current_buf()
-- Skip if we already handled this buffer
if auto_opened_buffers[bufnr] then
return
end
-- Skip if we already handled this buffer
if auto_opened_buffers[bufnr] then
return
end
local current_file = vim.fn.expand("%:p")
local current_file = vim.fn.expand("%:p")
-- Skip empty paths
if not current_file or current_file == "" then
return
end
-- Skip empty paths
if not current_file or current_file == "" then
return
end
-- Verify it's a coder file
if not utils.is_coder_file(current_file) then
return
end
-- Verify it's a coder file
if not utils.is_coder_file(current_file) then
return
end
-- Skip if we're in a special buffer (nvim-tree, etc.)
local buftype = vim.bo[bufnr].buftype
if buftype ~= "" then
return
end
-- Skip if we're in a special buffer (nvim-tree, etc.)
local buftype = vim.bo[bufnr].buftype
if buftype ~= "" then
return
end
-- Mark as handled
auto_opened_buffers[bufnr] = true
-- Mark as handled
auto_opened_buffers[bufnr] = true
-- Get the target file path
local target_path = utils.get_target_path(current_file)
-- Get the target file path
local target_path = utils.get_target_path(current_file)
-- Check if target file exists
if not utils.file_exists(target_path) then
utils.notify("Target file not found: " .. vim.fn.fnamemodify(target_path, ":t"), vim.log.levels.WARN)
return
end
-- Check if target file exists
if not utils.file_exists(target_path) then
utils.notify("Target file not found: " .. vim.fn.fnamemodify(target_path, ":t"), vim.log.levels.WARN)
return
end
-- Get config with fallback defaults
local codetyper = require("codetyper")
local config = codetyper.get_config()
-- Get config with fallback defaults
local codetyper = require("codetyper")
local config = codetyper.get_config()
-- Fallback width if config not fully loaded
local width = (config and config.window and config.window.width) or 0.4
if width <= 1 then
width = math.floor(vim.o.columns * width)
end
-- Fallback width if config not fully loaded
local width = (config and config.window and config.window.width) or 0.4
if width <= 1 then
width = math.floor(vim.o.columns * width)
end
-- Store current coder window
local coder_win = vim.api.nvim_get_current_win()
local coder_buf = bufnr
-- Store current coder window
local coder_win = vim.api.nvim_get_current_win()
local coder_buf = bufnr
-- Open target file in a vertical split on the right
local ok, err = pcall(function()
vim.cmd("vsplit " .. vim.fn.fnameescape(target_path))
end)
-- Open target file in a vertical split on the right
local ok, err = pcall(function()
vim.cmd("vsplit " .. vim.fn.fnameescape(target_path))
end)
if not ok then
utils.notify("Failed to open target file: " .. tostring(err), vim.log.levels.ERROR)
auto_opened_buffers[bufnr] = nil -- Allow retry
return
end
if not ok then
utils.notify("Failed to open target file: " .. tostring(err), vim.log.levels.ERROR)
auto_opened_buffers[bufnr] = nil -- Allow retry
return
end
-- Now we're in the target window (right side)
local target_win = vim.api.nvim_get_current_win()
local target_buf = vim.api.nvim_get_current_buf()
-- Now we're in the target window (right side)
local target_win = vim.api.nvim_get_current_win()
local target_buf = vim.api.nvim_get_current_buf()
-- Set the coder window width (left side)
pcall(vim.api.nvim_win_set_width, coder_win, width)
-- Set the coder window width (left side)
pcall(vim.api.nvim_win_set_width, coder_win, width)
-- Update window module state
window._coder_win = coder_win
window._coder_buf = coder_buf
window._target_win = target_win
window._target_buf = target_buf
-- Update window module state
window._coder_win = coder_win
window._coder_buf = coder_buf
window._target_win = target_win
window._target_buf = target_buf
-- Set up window options for coder window
pcall(function()
vim.wo[coder_win].number = true
vim.wo[coder_win].relativenumber = true
vim.wo[coder_win].signcolumn = "yes"
end)
-- Set up window options for coder window
pcall(function()
vim.wo[coder_win].number = true
vim.wo[coder_win].relativenumber = true
vim.wo[coder_win].signcolumn = "yes"
end)
utils.notify("Opened target: " .. vim.fn.fnamemodify(target_path, ":t"))
utils.notify("Opened target: " .. vim.fn.fnamemodify(target_path, ":t"))
end
--- Clear auto-opened tracking for a buffer
---@param bufnr number Buffer number
function M.clear_auto_opened(bufnr)
auto_opened_buffers[bufnr] = nil
auto_opened_buffers[bufnr] = nil
end
--- Set appropriate filetype for coder files
function M.set_coder_filetype()
local filepath = vim.fn.expand("%:p")
local filepath = vim.fn.expand("%:p")
-- Extract the actual extension (e.g., index.coder.ts -> ts)
local ext = filepath:match("%.coder%.(%w+)$")
-- Extract the actual extension (e.g., index.coder.ts -> ts)
local ext = filepath:match("%.coder%.(%w+)$")
if ext then
-- Map extension to filetype
local ft_map = {
ts = "typescript",
tsx = "typescriptreact",
js = "javascript",
jsx = "javascriptreact",
py = "python",
lua = "lua",
go = "go",
rs = "rust",
rb = "ruby",
java = "java",
c = "c",
cpp = "cpp",
cs = "cs",
json = "json",
yaml = "yaml",
yml = "yaml",
md = "markdown",
html = "html",
css = "css",
scss = "scss",
vue = "vue",
svelte = "svelte",
}
if ext then
-- Map extension to filetype
local ft_map = {
ts = "typescript",
tsx = "typescriptreact",
js = "javascript",
jsx = "javascriptreact",
py = "python",
lua = "lua",
go = "go",
rs = "rust",
rb = "ruby",
java = "java",
c = "c",
cpp = "cpp",
cs = "cs",
json = "json",
yaml = "yaml",
yml = "yaml",
md = "markdown",
html = "html",
css = "css",
scss = "scss",
vue = "vue",
svelte = "svelte",
}
local filetype = ft_map[ext] or ext
vim.bo.filetype = filetype
end
local filetype = ft_map[ext] or ext
vim.bo.filetype = filetype
end
end
--- Clear all autocommands
function M.clear()
vim.api.nvim_del_augroup_by_name(AUGROUP)
vim.api.nvim_del_augroup_by_name(AUGROUP)
end
return M

View File

@@ -88,6 +88,23 @@ local function cmd_toggle()
window.toggle_split(target_path, coder_path)
end
--- Build enhanced user prompt with context
---@param clean_prompt string The cleaned user prompt
---@param context table Context information
---@return string Enhanced prompt
local function build_user_prompt(clean_prompt, context)
local enhanced = "TASK: " .. clean_prompt .. "\n\n"
enhanced = enhanced .. "REQUIREMENTS:\n"
enhanced = enhanced .. "- Generate ONLY " .. (context.language or "code") .. " code\n"
enhanced = enhanced .. "- NO markdown code blocks (no ```)\n"
enhanced = enhanced .. "- NO explanations or comments about what you did\n"
enhanced = enhanced .. "- Match the coding style of the existing file exactly\n"
enhanced = enhanced .. "- Output must be ready to insert directly into the file\n"
return enhanced
end
--- Process prompt at cursor and generate code
local function cmd_process()
local parser = require("codetyper.parser")
@@ -111,8 +128,13 @@ local function cmd_process()
local prompt_type = parser.detect_prompt_type(prompt.content)
local context = llm.build_context(target_path, prompt_type)
local clean_prompt = parser.clean_prompt(prompt.content)
-- Build enhanced prompt with explicit instructions
local enhanced_prompt = build_user_prompt(clean_prompt, context)
llm.generate(clean_prompt, context, function(response, err)
utils.notify("Processing: " .. clean_prompt:sub(1, 50) .. "...", vim.log.levels.INFO)
llm.generate(enhanced_prompt, context, function(response, err)
if err then
utils.notify("Generation failed: " .. err, vim.log.levels.ERROR)
return
@@ -122,6 +144,7 @@ local function cmd_process()
-- Inject code into target file
local inject = require("codetyper.inject")
inject.inject_code(target_path, response, prompt_type)
utils.notify("Code generated and injected!", vim.log.levels.INFO)
end
end)
end
@@ -244,6 +267,332 @@ local function cmd_focus()
end
end
--- Transform inline /@ @/ tags in current file
--- Works on ANY file, not just .coder.* files
local function cmd_transform()
local parser = require("codetyper.parser")
local llm = require("codetyper.llm")
local bufnr = vim.api.nvim_get_current_buf()
local filepath = vim.fn.expand("%:p")
if filepath == "" then
utils.notify("No file in current buffer", vim.log.levels.WARN)
return
end
-- Find all prompts in the current buffer
local prompts = parser.find_prompts_in_buffer(bufnr)
if #prompts == 0 then
utils.notify("No /@ @/ tags found in current file", vim.log.levels.INFO)
return
end
utils.notify("Found " .. #prompts .. " prompt(s) to transform...", vim.log.levels.INFO)
-- Build context for this file
local ext = vim.fn.fnamemodify(filepath, ":e")
local context = llm.build_context(filepath, "code_generation")
-- Process prompts in reverse order (bottom to top) to maintain line numbers
local sorted_prompts = {}
for i = #prompts, 1, -1 do
table.insert(sorted_prompts, prompts[i])
end
-- Track how many are being processed
local pending = #sorted_prompts
local completed = 0
local errors = 0
-- Process each prompt
for _, prompt in ipairs(sorted_prompts) do
local clean_prompt = parser.clean_prompt(prompt.content)
local prompt_type = parser.detect_prompt_type(prompt.content)
-- Build enhanced user prompt
local enhanced_prompt = "TASK: " .. clean_prompt .. "\n\n"
enhanced_prompt = enhanced_prompt .. "REQUIREMENTS:\n"
enhanced_prompt = enhanced_prompt .. "- Generate ONLY " .. (context.language or "code") .. " code\n"
enhanced_prompt = enhanced_prompt .. "- NO markdown code blocks (no ```)\n"
enhanced_prompt = enhanced_prompt .. "- NO explanations or comments about what you did\n"
enhanced_prompt = enhanced_prompt .. "- Match the coding style of the existing file exactly\n"
enhanced_prompt = enhanced_prompt .. "- Output must be ready to insert directly into the file\n"
utils.notify("Processing: " .. clean_prompt:sub(1, 40) .. "...", vim.log.levels.INFO)
-- Generate code for this prompt
llm.generate(enhanced_prompt, context, function(response, err)
if err then
utils.notify("Failed: " .. err, vim.log.levels.ERROR)
errors = errors + 1
elseif response then
-- Replace the prompt tag with generated code
vim.schedule(function()
-- Get current buffer lines
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-- Calculate the exact range to replace
local start_line = prompt.start_line
local end_line = prompt.end_line
-- Find the full lines containing the tags
local start_line_content = lines[start_line] or ""
local end_line_content = lines[end_line] or ""
-- Check if there's content before the opening tag on the same line
local codetyper = require("codetyper")
local config = codetyper.get_config()
local before_tag = ""
local after_tag = ""
local open_pos = start_line_content:find(utils.escape_pattern(config.patterns.open_tag))
if open_pos and open_pos > 1 then
before_tag = start_line_content:sub(1, open_pos - 1)
end
local close_pos = end_line_content:find(utils.escape_pattern(config.patterns.close_tag))
if close_pos then
local after_close = close_pos + #config.patterns.close_tag
if after_close <= #end_line_content then
after_tag = end_line_content:sub(after_close)
end
end
-- Build the replacement lines
local replacement_lines = vim.split(response, "\n", { plain = true })
-- Add before/after content if any
if before_tag ~= "" and #replacement_lines > 0 then
replacement_lines[1] = before_tag .. replacement_lines[1]
end
if after_tag ~= "" and #replacement_lines > 0 then
replacement_lines[#replacement_lines] = replacement_lines[#replacement_lines] .. after_tag
end
-- Replace the lines in buffer
vim.api.nvim_buf_set_lines(bufnr, start_line - 1, end_line, false, replacement_lines)
completed = completed + 1
if completed + errors >= pending then
utils.notify(
"Transform complete: " .. completed .. " succeeded, " .. errors .. " failed",
errors > 0 and vim.log.levels.WARN or vim.log.levels.INFO
)
end
end)
end
end)
end
end
--- Transform prompts within a line range (for visual selection)
---@param start_line number Start line (1-indexed)
---@param end_line number End line (1-indexed)
local function cmd_transform_range(start_line, end_line)
local parser = require("codetyper.parser")
local llm = require("codetyper.llm")
local bufnr = vim.api.nvim_get_current_buf()
local filepath = vim.fn.expand("%:p")
if filepath == "" then
utils.notify("No file in current buffer", vim.log.levels.WARN)
return
end
-- Find all prompts in the current buffer
local all_prompts = parser.find_prompts_in_buffer(bufnr)
-- Filter prompts that are within the selected range
local prompts = {}
for _, prompt in ipairs(all_prompts) do
if prompt.start_line >= start_line and prompt.end_line <= end_line then
table.insert(prompts, prompt)
end
end
if #prompts == 0 then
utils.notify("No /@ @/ tags found in selection (lines " .. start_line .. "-" .. end_line .. ")", vim.log.levels.INFO)
return
end
utils.notify("Found " .. #prompts .. " prompt(s) in selection to transform...", vim.log.levels.INFO)
-- Build context for this file
local context = llm.build_context(filepath, "code_generation")
-- Process prompts in reverse order (bottom to top) to maintain line numbers
local sorted_prompts = {}
for i = #prompts, 1, -1 do
table.insert(sorted_prompts, prompts[i])
end
local pending = #sorted_prompts
local completed = 0
local errors = 0
for _, prompt in ipairs(sorted_prompts) do
local clean_prompt = parser.clean_prompt(prompt.content)
local enhanced_prompt = "TASK: " .. clean_prompt .. "\n\n"
enhanced_prompt = enhanced_prompt .. "REQUIREMENTS:\n"
enhanced_prompt = enhanced_prompt .. "- Generate ONLY " .. (context.language or "code") .. " code\n"
enhanced_prompt = enhanced_prompt .. "- NO markdown code blocks (no ```)\n"
enhanced_prompt = enhanced_prompt .. "- NO explanations or comments about what you did\n"
enhanced_prompt = enhanced_prompt .. "- Match the coding style of the existing file exactly\n"
enhanced_prompt = enhanced_prompt .. "- Output must be ready to insert directly into the file\n"
utils.notify("Processing: " .. clean_prompt:sub(1, 40) .. "...", vim.log.levels.INFO)
llm.generate(enhanced_prompt, context, function(response, err)
if err then
utils.notify("Failed: " .. err, vim.log.levels.ERROR)
errors = errors + 1
elseif response then
vim.schedule(function()
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local p_start_line = prompt.start_line
local p_end_line = prompt.end_line
local start_line_content = lines[p_start_line] or ""
local end_line_content = lines[p_end_line] or ""
local codetyper = require("codetyper")
local config = codetyper.get_config()
local before_tag = ""
local after_tag = ""
local open_pos = start_line_content:find(utils.escape_pattern(config.patterns.open_tag))
if open_pos and open_pos > 1 then
before_tag = start_line_content:sub(1, open_pos - 1)
end
local close_pos = end_line_content:find(utils.escape_pattern(config.patterns.close_tag))
if close_pos then
local after_close = close_pos + #config.patterns.close_tag
if after_close <= #end_line_content then
after_tag = end_line_content:sub(after_close)
end
end
local replacement_lines = vim.split(response, "\n", { plain = true })
if before_tag ~= "" and #replacement_lines > 0 then
replacement_lines[1] = before_tag .. replacement_lines[1]
end
if after_tag ~= "" and #replacement_lines > 0 then
replacement_lines[#replacement_lines] = replacement_lines[#replacement_lines] .. after_tag
end
vim.api.nvim_buf_set_lines(bufnr, p_start_line - 1, p_end_line, false, replacement_lines)
completed = completed + 1
if completed + errors >= pending then
utils.notify(
"Transform complete: " .. completed .. " succeeded, " .. errors .. " failed",
errors > 0 and vim.log.levels.WARN or vim.log.levels.INFO
)
end
end)
end
end)
end
end
--- Command wrapper for visual selection transform
local function cmd_transform_visual()
-- Get visual selection marks
local start_line = vim.fn.line("'<")
local end_line = vim.fn.line("'>")
cmd_transform_range(start_line, end_line)
end
--- Transform a single prompt at cursor position
local function cmd_transform_at_cursor()
local parser = require("codetyper.parser")
local llm = require("codetyper.llm")
local bufnr = vim.api.nvim_get_current_buf()
local filepath = vim.fn.expand("%:p")
if filepath == "" then
utils.notify("No file in current buffer", vim.log.levels.WARN)
return
end
-- Find prompt at cursor
local prompt = parser.get_prompt_at_cursor(bufnr)
if not prompt then
utils.notify("No /@ @/ tag at cursor position", vim.log.levels.WARN)
return
end
local clean_prompt = parser.clean_prompt(prompt.content)
local context = llm.build_context(filepath, "code_generation")
-- Build enhanced user prompt
local enhanced_prompt = "TASK: " .. clean_prompt .. "\n\n"
enhanced_prompt = enhanced_prompt .. "REQUIREMENTS:\n"
enhanced_prompt = enhanced_prompt .. "- Generate ONLY " .. (context.language or "code") .. " code\n"
enhanced_prompt = enhanced_prompt .. "- NO markdown code blocks (no ```)\n"
enhanced_prompt = enhanced_prompt .. "- NO explanations or comments about what you did\n"
enhanced_prompt = enhanced_prompt .. "- Match the coding style of the existing file exactly\n"
enhanced_prompt = enhanced_prompt .. "- Output must be ready to insert directly into the file\n"
utils.notify("Transforming: " .. clean_prompt:sub(1, 40) .. "...", vim.log.levels.INFO)
llm.generate(enhanced_prompt, context, function(response, err)
if err then
utils.notify("Transform failed: " .. err, vim.log.levels.ERROR)
return
end
if response then
vim.schedule(function()
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local start_line = prompt.start_line
local end_line = prompt.end_line
local start_line_content = lines[start_line] or ""
local end_line_content = lines[end_line] or ""
local codetyper = require("codetyper")
local config = codetyper.get_config()
local before_tag = ""
local after_tag = ""
local open_pos = start_line_content:find(utils.escape_pattern(config.patterns.open_tag))
if open_pos and open_pos > 1 then
before_tag = start_line_content:sub(1, open_pos - 1)
end
local close_pos = end_line_content:find(utils.escape_pattern(config.patterns.close_tag))
if close_pos then
local after_close = close_pos + #config.patterns.close_tag
if after_close <= #end_line_content then
after_tag = end_line_content:sub(after_close)
end
end
local replacement_lines = vim.split(response, "\n", { plain = true })
if before_tag ~= "" and #replacement_lines > 0 then
replacement_lines[1] = before_tag .. replacement_lines[1]
end
if after_tag ~= "" and #replacement_lines > 0 then
replacement_lines[#replacement_lines] = replacement_lines[#replacement_lines] .. after_tag
end
vim.api.nvim_buf_set_lines(bufnr, start_line - 1, end_line, false, replacement_lines)
utils.notify("Transform complete!", vim.log.levels.INFO)
end)
end
end)
end
--- Main command dispatcher
---@param args table Command arguments
local function coder_cmd(args)
@@ -264,6 +613,8 @@ local function coder_cmd(args)
["ask-toggle"] = cmd_ask_toggle,
["ask-clear"] = cmd_ask_clear,
gitignore = cmd_gitignore,
transform = cmd_transform,
["transform-cursor"] = cmd_transform_at_cursor,
}
local cmd_fn = commands[subcommand]
@@ -283,6 +634,7 @@ function M.setup()
"open", "close", "toggle", "process", "status", "focus",
"tree", "tree-view", "reset", "gitignore",
"ask", "ask-close", "ask-toggle", "ask-clear",
"transform", "transform-cursor",
}
end,
desc = "Codetyper.nvim commands",
@@ -325,6 +677,45 @@ function M.setup()
vim.api.nvim_create_user_command("CoderAskClear", function()
cmd_ask_clear()
end, { desc = "Clear Ask history" })
-- Transform commands (inline /@ @/ tag replacement)
vim.api.nvim_create_user_command("CoderTransform", function()
cmd_transform()
end, { desc = "Transform all /@ @/ tags in current file" })
vim.api.nvim_create_user_command("CoderTransformCursor", function()
cmd_transform_at_cursor()
end, { desc = "Transform /@ @/ tag at cursor" })
vim.api.nvim_create_user_command("CoderTransformVisual", function(opts)
local start_line = opts.line1
local end_line = opts.line2
cmd_transform_range(start_line, end_line)
end, { range = true, desc = "Transform /@ @/ tags in visual selection" })
-- Setup default keymaps
M.setup_keymaps()
end
--- Setup default keymaps for transform commands
function M.setup_keymaps()
-- Visual mode: transform selected /@ @/ tags
vim.keymap.set("v", "<leader>ctt", ":<C-u>CoderTransformVisual<CR>", {
silent = true,
desc = "Coder: Transform selected tags"
})
-- Normal mode: transform tag at cursor
vim.keymap.set("n", "<leader>ctt", "<cmd>CoderTransformCursor<CR>", {
silent = true,
desc = "Coder: Transform tag at cursor"
})
-- Normal mode: transform all tags in file
vim.keymap.set("n", "<leader>ctT", "<cmd>CoderTransform<CR>", {
silent = true,
desc = "Coder: Transform all tags in file"
})
end
return M

View File

@@ -5,14 +5,14 @@ local M = {}
---@type CoderConfig
local defaults = {
llm = {
provider = "claude",
provider = "ollama",
claude = {
api_key = nil, -- Will use ANTHROPIC_API_KEY env var if nil
model = "claude-sonnet-4-20250514",
},
ollama = {
host = "http://localhost:11434",
model = "codellama",
model = "deepseek-coder:6.7b",
},
},
window = {

View File

@@ -1,22 +1,22 @@
---@mod codetyper.llm LLM interface for Codetyper.nvim
local M = {}
local lang_map = require("codetyper.utils.langmap")
local utils = require("codetyper.utils")
--- Get the appropriate LLM client based on configuration
---@return table LLM client module
function M.get_client()
local codetyper = require("codetyper")
local config = codetyper.get_config()
local codetyper = require("codetyper")
local config = codetyper.get_config()
if config.llm.provider == "claude" then
return require("codetyper.llm.claude")
elseif config.llm.provider == "ollama" then
return require("codetyper.llm.ollama")
else
error("Unknown LLM provider: " .. config.llm.provider)
end
if config.llm.provider == "claude" then
return require("codetyper.llm.claude")
elseif config.llm.provider == "ollama" then
return require("codetyper.llm.ollama")
else
error("Unknown LLM provider: " .. config.llm.provider)
end
end
--- Generate code from a prompt
@@ -24,31 +24,40 @@ end
---@param context table Context information (file content, language, etc.)
---@param callback fun(response: string|nil, error: string|nil) Callback function
function M.generate(prompt, context, callback)
local client = M.get_client()
client.generate(prompt, context, callback)
local client = M.get_client()
client.generate(prompt, context, callback)
end
--- Build the system prompt for code generation
---@param context table Context information
---@return string System prompt
function M.build_system_prompt(context)
local prompts = require("codetyper.prompts")
-- Select appropriate system prompt based on context
local prompt_type = context.prompt_type or "code_generation"
local system_prompts = prompts.system
local system = system_prompts[prompt_type] or system_prompts.code_generation
-- Substitute variables
system = system:gsub("{{language}}", context.language or "unknown")
system = system:gsub("{{filepath}}", context.file_path or "unknown")
local prompts = require("codetyper.prompts")
if context.file_content then
system = system .. "\n\nExisting file content:\n```\n" .. context.file_content .. "\n```"
end
-- Select appropriate system prompt based on context
local prompt_type = context.prompt_type or "code_generation"
local system_prompts = prompts.system
return system
local system = system_prompts[prompt_type] or system_prompts.code_generation
-- Substitute variables
system = system:gsub("{{language}}", context.language or "unknown")
system = system:gsub("{{filepath}}", context.file_path or "unknown")
-- Add file content with analysis hints
if context.file_content and context.file_content ~= "" then
system = system .. "\n\n===== EXISTING FILE CONTENT (analyze and match this style) =====\n"
system = system .. context.file_content
system = system .. "\n===== END OF EXISTING FILE =====\n"
system = system .. "\nYour generated code MUST follow the exact patterns shown above."
else
system = system
.. "\n\nThis is a new/empty file. Generate clean, idiomatic "
.. (context.language or "code")
.. " following best practices."
end
return system
end
--- Build context for LLM request
@@ -56,46 +65,49 @@ end
---@param prompt_type string Type of prompt
---@return table Context object
function M.build_context(target_path, prompt_type)
local content = utils.read_file(target_path)
local ext = vim.fn.fnamemodify(target_path, ":e")
local content = utils.read_file(target_path)
local ext = vim.fn.fnamemodify(target_path, ":e")
-- Map extension to language
local lang_map = {
ts = "TypeScript",
tsx = "TypeScript React",
js = "JavaScript",
jsx = "JavaScript React",
py = "Python",
lua = "Lua",
go = "Go",
rs = "Rust",
rb = "Ruby",
java = "Java",
c = "C",
cpp = "C++",
cs = "C#",
}
return {
file_content = content,
language = lang_map[ext] or ext,
extension = ext,
prompt_type = prompt_type,
file_path = target_path,
}
return {
file_content = content,
language = lang_map[ext] or ext,
extension = ext,
prompt_type = prompt_type,
file_path = target_path,
}
end
--- Parse LLM response and extract code
---@param response string Raw LLM response
---@return string Extracted code
function M.extract_code(response)
-- Remove markdown code blocks if present
local code = response:gsub("```%w*\n?", ""):gsub("\n?```", "")
local code = response
-- Trim whitespace
code = code:match("^%s*(.-)%s*$")
-- Remove markdown code blocks with language tags (```typescript, ```javascript, etc.)
code = code:gsub("```%w+%s*\n", "")
code = code:gsub("```%w+%s*$", "")
code = code:gsub("^```%w*\n?", "")
code = code:gsub("\n?```%s*$", "")
code = code:gsub("\n```\n", "\n")
code = code:gsub("```", "")
return code
-- Remove common explanation prefixes that LLMs sometimes add
code = code:gsub("^Here.-:\n", "")
code = code:gsub("^Here's.-:\n", "")
code = code:gsub("^This.-:\n", "")
code = code:gsub("^The following.-:\n", "")
code = code:gsub("^Below.-:\n", "")
-- Remove common explanation suffixes
code = code:gsub("\n\nThis code.-$", "")
code = code:gsub("\n\nThe above.-$", "")
code = code:gsub("\n\nNote:.-$", "")
code = code:gsub("\n\nExplanation:.-$", "")
-- Trim leading/trailing whitespace but preserve internal formatting
code = code:match("^%s*(.-)%s*$") or code
return code
end
return M

View File

@@ -0,0 +1,46 @@
---@mod codetyper.prompts.agent Agent prompts for Codetyper.nvim
---
--- System prompts for the agentic mode with tool use.
local M = {}
--- System prompt for agent mode
M.system = [[You are an AI coding agent integrated into Neovim via Codetyper.nvim.
You can read files, edit code, write new files, and run bash commands to help the user.
You have access to the following tools:
- read_file: Read file contents
- edit_file: Edit a file by finding and replacing specific content
- write_file: Write or create a file
- bash: Execute shell commands
GUIDELINES:
1. Always read a file before editing it to understand its current state
2. Use edit_file for targeted changes (find and replace specific content)
3. Use write_file only for new files or complete rewrites
4. Be conservative with bash commands - only run what's necessary
5. After making changes, summarize what you did
6. If a task requires multiple steps, think through the plan first
IMPORTANT:
- Be precise with edit_file - the "find" content must match exactly
- When editing, include enough context to make the match unique
- Never delete files without explicit user confirmation
- Always explain what you're doing and why
]]
--- Tool usage instructions appended to system prompt
M.tool_instructions = [[
When you need to use a tool, output the tool call in a JSON block.
After receiving the result, you can either call another tool or provide your final response.
SAFETY RULES:
- Never run destructive bash commands (rm -rf, etc.) without confirmation
- Always preserve existing functionality when editing
- If unsure about a change, ask for clarification first
]]
--- Prompt for when agent finishes
M.completion = [[Based on the tool results above, please provide a summary of what was done and any next steps the user should take.]]
return M

View File

@@ -14,6 +14,8 @@ Requirements:
- Include proper error handling
- Use appropriate types (if applicable)
- Make it efficient and readable
OUTPUT ONLY THE RAW CODE. No explanations, no markdown, no code fences.
]]
--- Prompt template for creating a new class/module
@@ -26,6 +28,8 @@ Requirements:
- Include constructor/initialization
- Implement proper encapsulation
- Add necessary methods as described
OUTPUT ONLY THE RAW CODE. No explanations, no markdown, no code fences.
]]
--- Prompt template for implementing an interface/trait
@@ -37,6 +41,8 @@ Requirements:
- Implement all required methods
- Follow the interface contract exactly
- Handle edge cases appropriately
OUTPUT ONLY THE RAW CODE. No explanations, no markdown, no code fences.
]]
--- Prompt template for creating a React component
@@ -49,6 +55,8 @@ Requirements:
- Include proper TypeScript types (if .tsx)
- Follow React best practices
- Make it reusable and composable
OUTPUT ONLY THE RAW CODE. No explanations, no markdown, no code fences.
]]
--- Prompt template for creating an API endpoint
@@ -61,6 +69,8 @@ Requirements:
- Proper error handling and status codes
- Follow RESTful conventions
- Include appropriate middleware
OUTPUT ONLY THE RAW CODE. No explanations, no markdown, no code fences.
]]
--- Prompt template for creating a utility function
@@ -73,6 +83,8 @@ Requirements:
- Handle edge cases
- Efficient implementation
- Well-typed (if applicable)
OUTPUT ONLY THE RAW CODE. No explanations, no markdown, no code fences.
]]
--- Prompt template for generic code generation
@@ -88,6 +100,8 @@ Requirements:
- Match existing code style
- Follow best practices
- Handle errors appropriately
OUTPUT ONLY THE RAW CODE. No explanations, no markdown, no code fences.
]]
return M

View File

@@ -5,20 +5,28 @@
local M = {}
--- Base system prompt for code generation
M.code_generation = [[You are an expert code generation assistant integrated into Neovim via Codetyper.nvim.
Your task is to generate high-quality, production-ready code based on the user's prompt.
M.code_generation = [[You are an expert code generation assistant integrated into Neovim.
Your task is to generate production-ready {{language}} code that EXACTLY matches the style of the existing file.
CRITICAL RULES:
1. Output ONLY the code - no explanations, no markdown code blocks, no comments about what you did
2. Match the coding style, conventions, and patterns of the existing file
3. Use proper indentation and formatting for the language
4. Follow best practices for the specific language/framework
5. Preserve existing functionality unless explicitly asked to change it
6. Use meaningful variable and function names
7. Handle edge cases and errors appropriately
ABSOLUTE RULES - FOLLOW STRICTLY:
1. Output ONLY raw {{language}} code - NO explanations, NO markdown, NO code fences (```), NO comments about what you did
2. DO NOT wrap output in ``` or any markdown - just raw code
3. The output must be valid {{language}} code that can be directly inserted into the file
4. MATCH the existing code patterns in the file:
- Same indentation style (spaces/tabs)
- Same naming conventions (camelCase, snake_case, PascalCase, etc.)
- Same import/require style used in the file
- Same comment style
- Same function/class/module patterns used in the file
5. If the file has existing exports, follow the same export pattern
6. If the file uses certain libraries/frameworks, use the same ones
7. Include proper types/annotations if the language supports them and the file uses them
8. Include proper error handling following the file's patterns
Language: {{language}}
File: {{filepath}}
REMEMBER: Output ONLY valid {{language}} code. No markdown. No explanations. Just the code.
]]
--- System prompt for code explanation/ask
@@ -38,59 +46,72 @@ IMPORTANT: When file contents are provided, analyze them carefully and base your
]]
--- System prompt for refactoring
M.refactor = [[You are an expert code refactoring assistant integrated into Neovim via Codetyper.nvim.
Your task is to refactor code while maintaining its functionality.
M.refactor = [[You are an expert code refactoring assistant integrated into Neovim.
Your task is to refactor {{language}} code while maintaining its functionality.
CRITICAL RULES:
1. Output ONLY the refactored code - no explanations
2. Preserve ALL existing functionality
3. Improve code quality, readability, and maintainability
4. Follow SOLID principles and best practices
5. Keep the same coding style as the original
ABSOLUTE RULES - FOLLOW STRICTLY:
1. Output ONLY the refactored {{language}} code - NO explanations, NO markdown, NO code fences (```)
2. DO NOT wrap output in ``` or any markdown - just raw code
3. Preserve ALL existing functionality
4. Improve code quality, readability, and maintainability
5. Keep the EXACT same coding style as the original file
6. Do not add new features unless explicitly requested
7. Optimize performance where possible without sacrificing readability
7. Output must be valid {{language}} code ready to replace the original
Language: {{language}}
REMEMBER: Output ONLY valid {{language}} code. No markdown. No explanations.
]]
--- System prompt for documentation
M.document = [[You are a documentation expert integrated into Neovim via Codetyper.nvim.
Your task is to generate clear, comprehensive documentation for code.
M.document = [[You are a documentation expert integrated into Neovim.
Your task is to generate documentation comments for {{language}} code.
CRITICAL RULES:
1. Output ONLY the documentation/comments - ready to be inserted into code
2. Use the appropriate documentation format for the language:
- JavaScript/TypeScript: JSDoc
- Python: Docstrings (Google or NumPy style)
- Lua: LuaDoc/EmmyLua
- Go: GoDoc
- Rust: RustDoc
- Java: Javadoc
3. Document all parameters, return values, and exceptions
4. Include usage examples where helpful
5. Be concise but complete
ABSOLUTE RULES - FOLLOW STRICTLY:
1. Output ONLY the documentation comments - NO explanations, NO markdown
2. DO NOT wrap output in ``` or any markdown - just raw comments
3. Use the appropriate documentation format for {{language}}:
- JavaScript/TypeScript/JSX/TSX: JSDoc (/** ... */)
- Python: Docstrings (triple quotes)
- Lua: LuaDoc/EmmyLua (---)
- Go: GoDoc comments
- Rust: RustDoc (///)
- Ruby: YARD
- PHP: PHPDoc
- Java/Kotlin: Javadoc
- C/C++: Doxygen
4. Document all parameters, return values, and exceptions
5. Output must be valid comment syntax for {{language}}
Language: {{language}}
REMEMBER: Output ONLY valid {{language}} documentation comments. No markdown.
]]
--- System prompt for test generation
M.test = [[You are a test generation expert integrated into Neovim via Codetyper.nvim.
Your task is to generate comprehensive unit tests for the provided code.
M.test = [[You are a test generation expert integrated into Neovim.
Your task is to generate unit tests for {{language}} code.
CRITICAL RULES:
1. Output ONLY the test code - no explanations
2. Use the appropriate testing framework for the language:
- JavaScript/TypeScript: Jest or Vitest
- Python: pytest
ABSOLUTE RULES - FOLLOW STRICTLY:
1. Output ONLY the test code - NO explanations, NO markdown, NO code fences (```)
2. DO NOT wrap output in ``` or any markdown - just raw test code
3. Use the appropriate testing framework for {{language}}:
- JavaScript/TypeScript/JSX/TSX: Jest, Vitest, or Mocha
- Python: pytest or unittest
- Lua: busted or plenary
- Go: testing package
- Rust: built-in tests
3. Cover happy paths, edge cases, and error scenarios
4. Use descriptive test names
- Rust: built-in #[test]
- Ruby: RSpec or Minitest
- PHP: PHPUnit
- Java/Kotlin: JUnit
- C/C++: Google Test or Catch2
4. Cover happy paths, edge cases, and error scenarios
5. Follow AAA pattern: Arrange, Act, Assert
6. Mock external dependencies appropriately
6. Output must be valid {{language}} test code
Language: {{language}}
REMEMBER: Output ONLY valid {{language}} test code. No markdown. No explanations.
]]
return M

View File

@@ -10,6 +10,19 @@ local CODER_FOLDER = ".coder"
--- Name of the tree log file
local TREE_LOG_FILE = "tree.log"
--- Name of the settings file
local SETTINGS_FILE = "settings.json"
--- Default settings for the coder folder
local DEFAULT_SETTINGS = {
["editor.fontSize"] = 14,
["editor.tabSize"] = 2,
["files.autoSave"] = "afterDelay",
["files.autoSaveDelay"] = 1000,
["terminal.integrated.fontSize"] = 14,
["workbench.colorTheme"] = "Default Dark+",
}
--- Get the path to the .coder folder
---@return string|nil Path to .coder folder or nil
function M.get_coder_folder()
@@ -30,6 +43,57 @@ function M.get_tree_log_path()
return coder_folder .. "/" .. TREE_LOG_FILE
end
--- Get the path to the settings.json file
---@return string|nil Path to settings.json or nil
function M.get_settings_path()
local coder_folder = M.get_coder_folder()
if not coder_folder then
return nil
end
return coder_folder .. "/" .. SETTINGS_FILE
end
--- Ensure settings.json exists with default settings
---@return boolean Success status
function M.ensure_settings()
local settings_path = M.get_settings_path()
if not settings_path then
return false
end
-- Check if file already exists
local stat = vim.loop.fs_stat(settings_path)
if stat then
return true -- File already exists, don't overwrite
end
-- Create settings file with defaults
local json_content = vim.fn.json_encode(DEFAULT_SETTINGS)
-- Pretty print the JSON
local ok, pretty_json = pcall(function()
return vim.fn.system({ "python3", "-m", "json.tool" }, json_content)
end)
if not ok or vim.v.shell_error ~= 0 then
-- Fallback to simple formatting if python not available
pretty_json = "{\n"
local keys = vim.tbl_keys(DEFAULT_SETTINGS)
table.sort(keys)
for i, key in ipairs(keys) do
local value = DEFAULT_SETTINGS[key]
local value_str = type(value) == "string" and ('"' .. value .. '"') or tostring(value)
pretty_json = pretty_json .. ' "' .. key .. '": ' .. value_str
if i < #keys then
pretty_json = pretty_json .. ","
end
pretty_json = pretty_json .. "\n"
end
pretty_json = pretty_json .. "}\n"
end
return utils.write_file(settings_path, pretty_json)
end
--- Ensure .coder folder exists
---@return boolean Success status
function M.ensure_coder_folder()
@@ -198,10 +262,51 @@ function M.update_tree_log()
return false
end
--- Cache to track initialized projects (by root path)
local initialized_projects = {}
--- Check if project is already initialized
---@param root string Project root path
---@return boolean
local function is_project_initialized(root)
return initialized_projects[root] == true
end
--- Initialize tree logging (called on setup)
function M.setup()
---@param force? boolean Force re-initialization even if cached
---@return boolean success
function M.setup(force)
local coder_folder = M.get_coder_folder()
if not coder_folder then
return false
end
local root = utils.get_project_root()
if not root then
return false
end
-- Skip if already initialized (unless forced)
if not force and is_project_initialized(root) then
return true
end
-- Ensure .coder folder exists
if not M.ensure_coder_folder() then
utils.notify("Failed to create .coder folder", vim.log.levels.ERROR)
return false
end
-- Create settings.json with defaults if it doesn't exist
M.ensure_settings()
-- Create initial tree log
M.update_tree_log()
-- Mark project as initialized
initialized_projects[root] = true
return true
end
--- Get file statistics from tree

View File

@@ -17,7 +17,8 @@ function M.get_project_root()
end
found = vim.fn.finddir(marker, current .. ";")
if found ~= "" then
return vim.fn.fnamemodify(found, ":p:h")
-- For directories, :p:h gives the dir itself, so we need :p:h:h to get parent
return vim.fn.fnamemodify(found, ":p:h:h")
end
end

View File

@@ -0,0 +1,75 @@
local lang_map = {
-- JavaScript/TypeScript
ts = "TypeScript",
tsx = "TypeScript React (TSX)",
js = "JavaScript",
jsx = "JavaScript React (JSX)",
mjs = "JavaScript (ESM)",
cjs = "JavaScript (CommonJS)",
-- Python
py = "Python",
pyw = "Python",
pyx = "Cython",
-- Systems languages
c = "C",
h = "C Header",
cpp = "C++",
hpp = "C++ Header",
cc = "C++",
cxx = "C++",
rs = "Rust",
go = "Go",
-- JVM languages
java = "Java",
kt = "Kotlin",
kts = "Kotlin Script",
scala = "Scala",
clj = "Clojure",
-- Web
html = "HTML",
css = "CSS",
scss = "SCSS",
sass = "Sass",
less = "Less",
vue = "Vue",
svelte = "Svelte",
-- Scripting
lua = "Lua",
rb = "Ruby",
php = "PHP",
pl = "Perl",
sh = "Shell (Bash)",
bash = "Bash",
zsh = "Zsh",
fish = "Fish",
ps1 = "PowerShell",
-- .NET
cs = "C#",
fs = "F#",
vb = "Visual Basic",
-- Data/Config
json = "JSON",
yaml = "YAML",
yml = "YAML",
toml = "TOML",
xml = "XML",
sql = "SQL",
graphql = "GraphQL",
-- Other
swift = "Swift",
dart = "Dart",
ex = "Elixir",
exs = "Elixir Script",
erl = "Erlang",
hs = "Haskell",
ml = "OCaml",
r = "R",
jl = "Julia",
nim = "Nim",
zig = "Zig",
v = "V",
md = "Markdown",
mdx = "MDX",
}
return lang_map

View File

@@ -1,121 +1,148 @@
-- Codetyper.nvim - AI-powered coding partner for Neovim
-- Plugin loader
local g = vim.g
local fn = vim.fn
local api = vim.api
local cmd = vim.cmd
-- Prevent loading twice
if vim.g.loaded_codetyper then
return
if g.loaded_codetyper then
return
end
vim.g.loaded_codetyper = true
g.loaded_codetyper = true
-- Minimum Neovim version check
if vim.fn.has("nvim-0.8.0") == 0 then
vim.api.nvim_err_writeln("Codetyper.nvim requires Neovim 0.8.0 or higher")
return
if fn.has("nvim-0.8.0") == 0 then
api.nvim_err_writeln("Codetyper.nvim requires Neovim 0.8.0 or higher")
return
end
--- Initialize codetyper plugin fully
--- Creates .coder folder, settings.json, tree.log, .gitignore
--- Also registers autocmds for /@ @/ prompt detection
---@return boolean success
local function init_coder_files()
local ok, err = pcall(function()
-- Full plugin initialization (includes config, commands, autocmds, tree, gitignore)
local codetyper = require("codetyper")
if not codetyper.is_initialized() then
codetyper.setup()
end
end)
if not ok then
vim.notify("[Codetyper] Failed to initialize: " .. tostring(err), vim.log.levels.ERROR)
return false
end
return true
end
-- Initialize .coder folder and tree.log on project open
vim.api.nvim_create_autocmd("VimEnter", {
callback = function()
-- Delay slightly to ensure cwd is set
vim.defer_fn(function()
local tree = require("codetyper.tree")
tree.setup()
-- Also ensure gitignore is updated
local gitignore = require("codetyper.gitignore")
gitignore.ensure_ignored()
end, 100)
end,
desc = "Initialize Codetyper .coder folder on startup",
api.nvim_create_autocmd("VimEnter", {
callback = function()
-- Delay slightly to ensure cwd is set
vim.defer_fn(function()
init_coder_files()
end, 100)
end,
desc = "Initialize Codetyper .coder folder on startup",
})
-- Also initialize on directory change
vim.api.nvim_create_autocmd("DirChanged", {
callback = function()
vim.defer_fn(function()
local tree = require("codetyper.tree")
tree.setup()
local gitignore = require("codetyper.gitignore")
gitignore.ensure_ignored()
end, 100)
end,
desc = "Initialize Codetyper .coder folder on directory change",
api.nvim_create_autocmd("DirChanged", {
callback = function()
vim.defer_fn(function()
init_coder_files()
end, 100)
end,
desc = "Initialize Codetyper .coder folder on directory change",
})
-- Auto-initialize when opening a coder file (for nvim-tree, telescope, etc.)
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile", "BufEnter" }, {
pattern = "*.coder.*",
callback = function()
-- Initialize plugin if not already done
local codetyper = require("codetyper")
if not codetyper.is_initialized() then
codetyper.setup()
end
end,
desc = "Auto-initialize Codetyper when opening coder files",
api.nvim_create_autocmd({ "BufRead", "BufNewFile", "BufEnter" }, {
pattern = "*.coder.*",
callback = function()
-- Initialize plugin if not already done
local codetyper = require("codetyper")
if not codetyper.is_initialized() then
codetyper.setup()
end
end,
desc = "Auto-initialize Codetyper when opening coder files",
})
-- Lazy-load the plugin on first command usage
vim.api.nvim_create_user_command("Coder", function(opts)
require("codetyper").setup()
-- Re-execute the command now that plugin is loaded
vim.cmd("Coder " .. (opts.args or ""))
api.nvim_create_user_command("Coder", function(opts)
require("codetyper").setup()
-- Re-execute the command now that plugin is loaded
cmd("Coder " .. (opts.args or ""))
end, {
nargs = "?",
complete = function()
return {
"open", "close", "toggle", "process", "status", "focus",
"tree", "tree-view", "reset", "gitignore",
"ask", "ask-close", "ask-toggle", "ask-clear",
}
end,
desc = "Codetyper.nvim commands",
nargs = "?",
complete = function()
return {
"open",
"close",
"toggle",
"process",
"status",
"focus",
"tree",
"tree-view",
"reset",
"gitignore",
"ask",
"ask-close",
"ask-toggle",
"ask-clear",
}
end,
desc = "Codetyper.nvim commands",
})
-- Lazy-load aliases
vim.api.nvim_create_user_command("CoderOpen", function()
require("codetyper").setup()
vim.cmd("CoderOpen")
api.nvim_create_user_command("CoderOpen", function()
require("codetyper").setup()
cmd("CoderOpen")
end, { desc = "Open Coder view" })
vim.api.nvim_create_user_command("CoderClose", function()
require("codetyper").setup()
vim.cmd("CoderClose")
api.nvim_create_user_command("CoderClose", function()
require("codetyper").setup()
cmd("CoderClose")
end, { desc = "Close Coder view" })
vim.api.nvim_create_user_command("CoderToggle", function()
require("codetyper").setup()
vim.cmd("CoderToggle")
api.nvim_create_user_command("CoderToggle", function()
require("codetyper").setup()
cmd("CoderToggle")
end, { desc = "Toggle Coder view" })
vim.api.nvim_create_user_command("CoderProcess", function()
require("codetyper").setup()
vim.cmd("CoderProcess")
api.nvim_create_user_command("CoderProcess", function()
require("codetyper").setup()
cmd("CoderProcess")
end, { desc = "Process prompt and generate code" })
vim.api.nvim_create_user_command("CoderTree", function()
require("codetyper").setup()
vim.cmd("CoderTree")
api.nvim_create_user_command("CoderTree", function()
require("codetyper").setup()
cmd("CoderTree")
end, { desc = "Refresh tree.log" })
vim.api.nvim_create_user_command("CoderTreeView", function()
require("codetyper").setup()
vim.cmd("CoderTreeView")
api.nvim_create_user_command("CoderTreeView", function()
require("codetyper").setup()
cmd("CoderTreeView")
end, { desc = "View tree.log" })
-- Ask panel commands
vim.api.nvim_create_user_command("CoderAsk", function()
require("codetyper").setup()
vim.cmd("CoderAsk")
api.nvim_create_user_command("CoderAsk", function()
require("codetyper").setup()
cmd("CoderAsk")
end, { desc = "Open Ask panel" })
vim.api.nvim_create_user_command("CoderAskToggle", function()
require("codetyper").setup()
vim.cmd("CoderAskToggle")
api.nvim_create_user_command("CoderAskToggle", function()
require("codetyper").setup()
cmd("CoderAskToggle")
end, { desc = "Toggle Ask panel" })
vim.api.nvim_create_user_command("CoderAskClear", function()
require("codetyper").setup()
vim.cmd("CoderAskClear")
api.nvim_create_user_command("CoderAskClear", function()
require("codetyper").setup()
cmd("CoderAskClear")
end, { desc = "Clear Ask history" })