Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .claude/rules/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Documentation

Documentation annotations follow the same style as Lua type annotations: no leading space after `---`.

```lua
-- correct
---@mod bufsitter.io IO
---@brief [[
---Treesitter-powered buffer manipulation.
---@brief ]]

-- incorrect
--- @mod bufsitter.io IO
--- @brief [[
```

Nested types use dot notation to separate namespaces, not underscores:

```lua
-- correct
---@class bufsitter.scratch.win.opts

-- incorrect
---@class bufsitter.scratch.win_opts
```

Example code blocks must declare every variable they use. Never assume `bufnr`, `cursor`, `s`, or any other variable is already in scope:

```lua
-- correct
--->lua
--- local cursor = require("bufsitter.cursor")
--- local items = cursor.root():children()(bufnr)
---<

-- incorrect
--->lua
--- local items = cursor.root():children()(bufnr)
---<
```

Body text inside annotation blocks (e.g. `---@brief`) may use a leading space for indentation purposes:

```lua
---@brief [[
---Top-level description.
---
--- Indented paragraph or example:
--->lua
--- require("bufsitter").setup()
---<
---@brief ]]
```
7 changes: 7 additions & 0 deletions .claude/rules/formatting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Formatting

Code must be formatted with StyLua. After any code change:
- Check: `stylua . --check` from the project root
- Format: `stylua .` from the project root

All formatting checks must pass before considering the task complete.
3 changes: 3 additions & 0 deletions .claude/rules/git.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Git

Never create commits unless the user explicitly asks for it.
20 changes: 20 additions & 0 deletions .claude/rules/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Security

Never hardcode absolute paths containing usernames or system-specific directories.
This applies to all files including source code, configuration, and settings files.

```lua
-- incorrect
local path = "/Users/username/folder/bufsitter.nvim/doc"

-- correct
local path = vim.fn.stdpath("data") .. "/bufsitter"
```

```json
// incorrect
{ "command": "cd /Users/username/folder/bufsitter.nvim && make test" }

// correct
{ "command": "make test" }
```
12 changes: 12 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Testing

After any code change, run `make test` from the project root and all tests must pass before considering the task complete.

Each test should be stateless and self-contained. Avoid sharing state between tests (e.g. global variables, module-level mutable state). Prefer `before_each` / `after_each` for setup and teardown over shared state, and only share state across tests when there is a clear and necessary reason to do so.

Every function must have tests covering a variety of scenarios (happy path, edge cases, failure cases). Keep each test minimal — only the setup and assertions strictly necessary to verify the scenario.

Prefer naming local variables before asserting, using one of these conventions:
- Input: `input`, `origin`
- Expected: `expected`, `expected_*`
- Actual: `actual`, `actual_*`
25 changes: 25 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git commit*)",
"command": "make test && make docs && git add doc/",
"timeout": 120,
"statusMessage": "Running tests and generating docs..."
},
{
"type": "command",
"if": "Bash(git push*)",
"command": "FROM=$(git merge-base HEAD @{u} 2>/dev/null) && commitlint --from \"$FROM\" --to HEAD --verbose",
"timeout": 30,
"statusMessage": "Running commitlint..."
}
]
}
]
}
}
21 changes: 21 additions & 0 deletions .commitlintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
extends: ["@commitlint/config-conventional"],
parserPreset: {
name: "conventional-changelog-conventionalcommits",
presetConfig: {
types: [
{ type: "feat", section: "Features" },
{ type: "fix", section: "Bug Fixes" },
{ type: "docs", section: "Documentation", hidden: false },
{ type: "perf", section: "Performance", hidden: false },
],
},
},
rules: {
'type-enum': [
2,
'always',
["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"],
]
}
};
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use nix
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: CI

on:
pull_request:
branches:
- main

jobs:
commitlint:
name: Commitlint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-node@v4
with:
node-version: lts/*

- name: Install commitlint
run: npm install --save-dev @commitlint/cli @commitlint/config-conventional conventional-changelog-conventionalcommits

- name: Lint commits
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose

test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: v0.12.1

- name: Run tests
run: make test

check-docs:
name: Check Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: stable

- name: Install lemmy-help
run: |
curl -sL https://github.com/numToStr/lemmy-help/releases/latest/download/lemmy-help-x86_64-unknown-linux-musl.tar.gz \
| tar -xz -C /usr/local/bin

- name: Check docs
run: make check-docs
19 changes: 19 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Release

on:
push:
branches:
- main

permissions:
contents: write
pull-requests: write

jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
release-type: simple
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Nix
.direnv/
result

# Editor
.DS_Store

# Local dev
.deps/
.nvim/
prompts/
.cache/
6 changes: 6 additions & 0 deletions .stylua.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
column_width = 90
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "Always"
55 changes: 55 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
DOCS_DIR ?= doc
DEPS_DIR = .deps/start
PARSER_DIR = .deps/parsers
NVIM_PARSER_DIR = $(shell nvim --headless -c "lua io.write(vim.fn.stdpath('data'))" -c "q" 2>/dev/null)/site/parser

$(DEPS_DIR)/plenary.nvim:
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim $@

$(DEPS_DIR)/nvim-treesitter:
git clone --depth 1 https://github.com/nvim-treesitter/nvim-treesitter $@

$(PARSER_DIR)/tree-sitter-go:
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-go $@

$(PARSER_DIR)/tree-sitter-typst:
git clone --depth 1 https://github.com/uben0/tree-sitter-typst $@

_deps: $(DEPS_DIR)/plenary.nvim $(DEPS_DIR)/nvim-treesitter

_install-parsers: _deps $(PARSER_DIR)/tree-sitter-go $(PARSER_DIR)/tree-sitter-typst
mkdir -p $(NVIM_PARSER_DIR)
gcc -shared -fPIC -o $(NVIM_PARSER_DIR)/go.so -I$(PARSER_DIR)/tree-sitter-go/src \
$(PARSER_DIR)/tree-sitter-go/src/parser.c
gcc -shared -fPIC -o $(NVIM_PARSER_DIR)/typst.so -I$(PARSER_DIR)/tree-sitter-typst/src \
$(PARSER_DIR)/tree-sitter-typst/src/parser.c \
$(PARSER_DIR)/tree-sitter-typst/src/scanner.c

test: _install-parsers
nvim \
--headless \
-u tests/minimal_init.lua \
-c "PlenaryBustedDirectory tests/ { minimal_init = 'tests/minimal_init.lua' }"
Comment on lines +28 to +32

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the test recipe, the continuation lines (--headless, -u ..., -c ...) appear to be indented with spaces rather than a leading tab. In Makefiles every recipe line must begin with a tab, otherwise make test can fail with a “missing separator”/parse error. Ensure each of these lines is part of the recipe (tab-indented), or rewrite this as a single tab-indented shell line.

Copilot uses AI. Check for mistakes.

_gen-docs:
mkdir -p $(DOCS_DIR)
lemmy-help -f -t \
lua/bufsitter/init.lua \
lua/bufsitter/cursor.lua \
lua/bufsitter/io.lua \
lua/bufsitter/ref.lua \
lua/bufsitter/scratch.lua \
> $(DOCS_DIR)/bufsitter.nvim.txt

docs:
$(MAKE) _gen-docs DOCS_DIR=doc
nvim --headless -c "helptags doc/" -c "q"

check-docs:
mkdir -p .cache/doc/expected .cache/doc/actual
cp doc/bufsitter.nvim.txt .cache/doc/expected/bufsitter.nvim.txt
$(MAKE) _gen-docs DOCS_DIR=.cache/doc/actual
diff .cache/doc/expected .cache/doc/actual

clean:
rm -rf .cache .deps
Loading
Loading