Skip to content

Commit 3d70acb

Browse files
committed
update
1 parent d5971a1 commit 3d70acb

File tree

9 files changed

+218
-163
lines changed

9 files changed

+218
-163
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- name: panvimdoc
2424
uses: kdheepak/panvimdoc@main
2525
with:
26-
vimdoc: guard.nvim
26+
vimdoc: fnpairs.nvim
2727
treesitter: true
2828
- uses: stefanzweifel/git-auto-commit-action@v4
2929
with:

README.md

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,19 @@
1-
# nvim-plugin-template
2-
3-
Neovim plugin template; includes automatic documentation generation from README, integration tests with Busted, and linting with Stylua
4-
5-
## Usage
6-
7-
1. Click `use this template` button generate a repo on your github.
8-
2. Clone your plugin repo. Open terminal then cd plugin directory.
9-
3. Run `python3 rename.py your-plugin-name`. This will replace all `nvim-plugin-template` to your `plugin-name`.
10-
Then it will prompt you input `y` or `n` to remove example codes in `init.lua` and
11-
`test/plugin_spec.lua`. If you are familiar this repo just input `y`. If you are looking at this template for the first time I suggest you inspect the contents. After this step `rename.py` will also auto-remove.
12-
13-
Now you have a clean plugin environment. Enjoy!
14-
15-
## Format
16-
17-
The CI uses `stylua` to format the code; customize the formatting by editing `.stylua.toml`.
18-
19-
## Test
20-
21-
See [Running tests locally](https://github.com/nvim-neorocks/nvim-busted-action?tab=readme-ov-file#running-tests-locally)
22-
23-
## CI
24-
25-
- Auto generates doc from README.
26-
- Runs the [nvim-busted-action](https://github.com/nvim-neorocks/nvim-busted-action) for test.
27-
- Lints with `stylua`.
28-
29-
## More
30-
31-
To see this template in action, take a look at my other plugins.
1+
# fnpairs.nvim
2+
3+
A simple pairs plugin written in FP style using Lua.
4+
5+
```lua
6+
-- BracketPair ADT
7+
local BracketPair = {
8+
match = {
9+
['('] = ')',
10+
['['] = ']',
11+
['{'] = '}',
12+
['"'] = '"',
13+
["'"] = "'",
14+
['`'] = '`',
15+
},
16+
}
17+
```
3218

3319
## License MIT
File renamed without changes.

doc/guard.nvim.txt

Lines changed: 0 additions & 60 deletions
This file was deleted.

lua/fnpairs/init.lua

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
---@diagnostic disable-next-line: redefined-local
2+
local F = {}
3+
4+
-- Core FP utilities
5+
F.curry = function(fn)
6+
local function curry_n(n, fn)
7+
if n <= 1 then
8+
return fn
9+
end
10+
return function(x)
11+
return curry_n(n - 1, function(...)
12+
return fn(x, ...)
13+
end)
14+
end
15+
end
16+
return curry_n(debug.getinfo(fn).nparams, fn)
17+
end
18+
19+
F.compose = function(...)
20+
local fns = { ... }
21+
return function(...)
22+
local result = ...
23+
for i = #fns, 1, -1 do
24+
result = fns[i](result)
25+
end
26+
return result
27+
end
28+
end
29+
30+
F.pipe = function(x, ...)
31+
local fns = { ... }
32+
local result = x
33+
for i = 1, #fns do
34+
result = fns[i](result)
35+
end
36+
return result
37+
end
38+
39+
-- Maybe Monad
40+
local Maybe = {}
41+
Maybe.of = function(x)
42+
return { value = x }
43+
end
44+
Maybe.nothing = Maybe.of(nil)
45+
Maybe.isNothing = function(maybe)
46+
return maybe.value == nil
47+
end
48+
Maybe.fromNullable = function(x)
49+
return x == nil and Maybe.nothing or Maybe.of(x)
50+
end
51+
52+
Maybe.map = F.curry(function(fn, maybe)
53+
if Maybe.isNothing(maybe) then
54+
return Maybe.nothing
55+
end
56+
return Maybe.of(fn(maybe.value))
57+
end)
58+
59+
Maybe.chain = F.curry(function(fn, maybe)
60+
if Maybe.isNothing(maybe) then
61+
return Maybe.nothing
62+
end
63+
return fn(maybe.value)
64+
end)
65+
66+
-- Pure state management
67+
local StateManager = {}
68+
StateManager.get = function()
69+
return {
70+
get_line = function()
71+
return vim.api.nvim_get_current_line()
72+
end,
73+
get_cursor = function()
74+
return vim.api.nvim_win_get_cursor(0)
75+
end,
76+
get_mode = function()
77+
return vim.api.nvim_get_mode().mode
78+
end,
79+
}
80+
end
81+
82+
-- State ADT
83+
local State = {}
84+
State.new = function()
85+
local state = StateManager.get()
86+
return {
87+
line = state.get_line(),
88+
cursor = state.get_cursor(),
89+
mode = state.get_mode(),
90+
}
91+
end
92+
93+
-- BracketPair ADT
94+
local BracketPair = {
95+
match = {
96+
['('] = ')',
97+
['['] = ']',
98+
['{'] = '}',
99+
['"'] = '"',
100+
["'"] = "'",
101+
['`'] = '`',
102+
},
103+
}
104+
105+
-- Action ADT with smart constructors
106+
local Action = {
107+
Skip = { type = 'skip' },
108+
Insert = function(opening, closing)
109+
return { type = 'insert', opening = opening, closing = closing }
110+
end,
111+
Move = { type = 'move' },
112+
Delete = { type = 'delete' },
113+
Nothing = { type = 'nothing' },
114+
}
115+
116+
-- Pure functions for character handling
117+
local get_char_at = F.curry(function(pos, state)
118+
return Maybe.fromNullable(state.line:sub(pos(state), pos(state)))
119+
end)
120+
121+
local get_char_before = get_char_at(function(state)
122+
return state.cursor[2]
123+
end)
124+
125+
local get_char_after = get_char_at(function(state)
126+
return state.cursor[2] + 1
127+
end)
128+
129+
-- Action determination functions
130+
local should_skip_completion = function(char)
131+
local state = State.new()
132+
local prev_char = get_char_before(state).value
133+
return char == "'" and prev_char and string.match(prev_char, '[%w]') and Action.Nothing
134+
or Action.Insert(char, BracketPair.match[char])
135+
end
136+
137+
local determine_char_action = function(char)
138+
return F.compose(should_skip_completion, function(action)
139+
return action or Action.Insert(char, BracketPair.match[char])
140+
end)
141+
end
142+
143+
local determine_action = F.curry(function(char, state)
144+
if state.mode == 'v' or state.mode == 'V' then
145+
return Action.Insert(char, BracketPair.match[char])
146+
end
147+
148+
return F.pipe(state, get_char_after, function(next_char)
149+
return next_char.value == BracketPair.match[char] and Action.Skip
150+
or determine_char_action(char)(char)
151+
end)
152+
end)
153+
154+
-- Action handlers
155+
local handle_skip = function()
156+
return '<Right>'
157+
end
158+
local handle_insert = function(action)
159+
return action.opening .. action.closing .. '<Left>'
160+
end
161+
162+
local handle_delete = function()
163+
local state = State.new()
164+
return F.pipe(state, function(s)
165+
return {
166+
before = get_char_before(s).value,
167+
after = get_char_after(s).value,
168+
}
169+
end, function(chars)
170+
return BracketPair.match[chars.before] == chars.after and '<BS><Del>' or '<BS>'
171+
end)
172+
end
173+
174+
local handle_action = function(action)
175+
local handlers = {
176+
skip = handle_skip,
177+
insert = handle_insert,
178+
delete = handle_delete,
179+
nothing = function()
180+
return ''
181+
end,
182+
}
183+
return handlers[action.type](action)
184+
end
185+
186+
return {
187+
setup = function()
188+
-- Setup bracket pairs
189+
for opening, _ in pairs(BracketPair.match) do
190+
vim.keymap.set('i', opening, function()
191+
return F.pipe(State.new(), determine_action(opening), handle_action)
192+
end, { expr = true, noremap = true })
193+
end
194+
195+
-- Setup backspace handling
196+
vim.keymap.set('i', '<BS>', function()
197+
return handle_action(Action.Delete)
198+
end, { expr = true, noremap = true })
199+
end,
200+
}

lua/nvim-plugin-template/init.lua

Lines changed: 0 additions & 7 deletions
This file was deleted.
File renamed without changes.

rename.py

Lines changed: 0 additions & 56 deletions
This file was deleted.

test/plugin_spec.lua

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +0,0 @@
1-
local example = require('nvim-plugin-template').example
2-
3-
describe('neovim plugin', function()
4-
it('work as expect', function()
5-
local result = example()
6-
assert.is_true(result)
7-
end)
8-
end)

0 commit comments

Comments
 (0)