Skip to content

Commit

Permalink
feat: extract callback in quoted symbol/list; deprecate callback in…
Browse files Browse the repository at this point in the history
… symbol (#150)

* test(autocmd): add specs for callback/command in macros

* test(command): apply fnlfmt

* test(command): add utils

* test(command): add specs for macro as callback

* test(keymap): add specs for macros in rhs

* refactor(util): simplify vim-fn extraction

* refactor(util): add `quoted?`

* refactor(util): add `second`

* refactor(util): add `->fn`

* feat(autocmd): detect quoted symbol as callback function

* feat(keymap): quoted symbol should be callback

* refactor(autocmd): with quote or not, vim.fn.foobar should be extracted to string

* feat(command): accept quoted callback format

* test(keymap): add specs for quoted callback patterns

* test(autocmd): define vim function `Test()` for every test

* test(autocmd): add specs for quoted callback patterns

* test(command): assert "Foo" is undefined before each tests

* test(command): add specs for quoted callback

* refactor(util): manipulate quote list simpler

* test(command): fix as update for quote handling

* docs(macros): update for quoted list

* docs(macros): update sample codes

* docs(macros): update for quoted callback

* docs(macros): update note for Vim script function

* docs(macros): udpate "Deprecated"

* feat(keymap): deprecate symbol as identifer for callback function

* feat(autocmd): deprecate callback in symbol for function

* test: replace `#` with "`" as behaviour has changed
  • Loading branch information
aileot committed Dec 20, 2022
1 parent 97a2948 commit 98f0dcd
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 85 deletions.
84 changes: 67 additions & 17 deletions doc/MACROS.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,27 +120,26 @@ Create or get an augroup, or override an existing augroup.
- `<callback>`: It indicates that `callback` must be callback function by
itself.
- `cb`: An alias of `<callback>` key.
- `callback`: (string|function) Set either callback function or vim Ex command.
Symbol, and anonymous function constructed by `fn`, `hashfn`, `lambda`, and
`partial`, is regarded as Lua function; otherwise, as Ex command.

Note: Insert `<command>` key in `extra-opts` to set string via symbol.

Note: Set `vim.fn.foobar`, or set `:foobar` to `callback` with `<command>` key
in `extra-opts`, to call Vim script function `foobar` without table arg from
`nvim_create_autocmd()`; instead, set `#(vim.fn.foobar $)` to call `foobar`
with the table arg.
- `callback`: (string|function) Set either callback function or Ex command. To
tell `callback` is Lua function, either prepend a quote `` ` `` as an
identifer (the quoted symbol, or list, is supposed to result in Lua function
at runtime), or set it in anonymous function constructed by `fn`, `hashfn`,
`lambda`, and `partial`; otherwise, Ex command.

Note: Set `` `vim.fn.foobar `` to call Vim script function `foobar` without
table argument from `nvim_create_autocmd()`; on the other hand, set
`#(vim.fn.foobar $)` to call `foobar` with the table argument.
- [`?api-opts`](#api-opts): (kv-table) `:h nvim_create_autocmd()`.

```fennel
(augroup! :sample-augroup
[:TextYankPost #(vim.highlight.on_yank {:timeout 450 :on_visual false})]
(autocmd! [:InsertEnter :InsertLeave]
[:<buffer> :desc "call foo#bar() without any args"] vim.fn.foo#bar)
[:<buffer> :desc "call foo#bar() without any args"] `vim.fn.foo#bar)
(autocmd! :VimEnter [:once :nested :desc "call baz#qux() with <amatch>"]
#(vim.fn.baz#qux $.match)))
(autocmd! :LspAttach
#(au! $.group :CursorHold [:buffer $.buf] vim.lsp.buf.document_highlight))
#(au! $.group :CursorHold [:buffer $.buf] `vim.lsp.buf.document_highlight))
```

is equivalent to
Expand Down Expand Up @@ -263,13 +262,56 @@ Map `lhs` to `rhs` in `modes`, non-recursively by default.
- `<callback>`: It indicates that `rhs` must be callback function by itself.
- `cb`: An alias of `<callback>` key.
- `lhs`: (string) Left-hand-side of the mapping.
- `rhs`: (string|function) Right-hand-side of the mapping. Symbol, and anonymous
function constructed by `fn`, `hashfn`, `lambda`, and `partial`, is regarded
as Lua function; otherwise, as Normal mode command execution.

Note: Insert `<command>` key in `extra-opts` to set string via symbol.
- `rhs`: (string|function) Right-hand-side of the mapping. Set either callback
function or Ex command. To tell `callback` is Lua function, either prepend a
quote `` ` `` as an identifer (the quoted symbol, or list, is supposed to
result in Lua function at runtime), or set it in anonymous function
constructed by `fn`, `hashfn`, `lambda`, and `partial`; otherwise, Ex command.

Note: To call Vim script function `foobar` without table arg from
`nvim_create_autocmd()`, just set `vim.fn.foobar`, or `` `vim.fn.foobar `` if
you prefer, there; on the other hand, set `#(vim.fn.foobar $)` to call
`foobar` with the table arg.
- [`?api-opts`](#api-opts): (kv-table) `:h nvim_set_keymap()`.

```fennel
(map! :i :jk :<Esc>)
(map! :n :lhs [:desc "call foo#bar()"] `vim.fn.foo#bar)
(map! [:n :x] [:remap :expr :literal] :d "&readonly ? '<Plug>(readonly-d)' : '<Plug>(noreadonly-d)'")
(map! [:n :x] [:remap :expr] :u #(if vim.bo.readonly
"<Plug>(readonly-u)"
"<Plug>(noreadonly-u)"))
```

is equivalent to

```vim
inoremap jk <Esc>
nnoremap lhs <Cmd>call foo#bar()<CR>
nmap <expr> d &readonly ? "\<Plug>(readonly-d)" : "\<Plug>(noreadonly-d)"
xmap <expr> u &readonly ? "\<Plug>(readonly-u)" : "\<Plug>(noreadonly-u)"
```

```lua
vim.keymap.set("i", "jk", "<Esc>")
vim.keymap.set("n", "lhs", function()
vim.fn["foo#bar"]()
end)
-- or, if you don't care about lazy loading,
vim.keymap.set("n", "lhs", vim.fn["foo#bar"])
vim.keymap.set({ "n", "x" }, "d", "&readonly ? '<Plug>(readonly-d)' : '<Plug>(noreadonly-d)'", {
remap = true,
expr = true,
replace_keycodes = false,
})
vim.keymap.set({ "n", "x" }, "u", function()
return vim.bo.readonly and "<Plug>(readonly-u)" or "<Plug>(noreadonly-u)"
end, {
remap = true,
expr = true,
})
```

#### `unmap!`

Delete keymap.
Expand Down Expand Up @@ -846,6 +888,14 @@ in another anonymous function is meaningless in many cases.

## Deprecated

### v0.5.1

- Symbol will no longer be an identifer as callback function for the macros,
[`map!`](#map!), [`autocmd!`](#autocmd), and so on; set `` `foobar `` to set a
symbol `foobar` as callback function instead.

### v0.5.0

- `nmap!`: Use [`map!`](#map) with `remap` option for corresponding mode
instead.
- `vmap!`: Use [`map!`](#map) with `remap` option for corresponding mode
Expand Down
65 changes: 50 additions & 15 deletions fnl/nvim-laurel/macros.fnl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
@return undefined"
(. xs 1))

(lambda second [xs]
"Return the second value in `xs`
@param xs sequence
@return undefined"
(. xs 2))

(lambda slice [xs ?start ?end]
"Return sequence from `?start` to `?end`.
@param xs sequence
Expand All @@ -89,6 +95,13 @@

;; Additional predicates ///2

(fn quoted? [x]
"Check if `x` is a list which begins with `quote`.
@param x any
@return boolean"
(and (list? x) ;
(= `quote (first x))))

(fn anonymous-function? [x]
"(Compile time) Check if type of `x` is anonymous function.
@param x any
Expand Down Expand Up @@ -156,14 +169,21 @@
(collect [k v (pairs ?api-opts) &into ?extra-opts]
(values k v))))

(fn ->unquoted [x]
"If quoted, return unquoted `x`; otherwise, just return `x` itself.
@param x any but nil
@return any"
(if (quoted? x)
(second x)
x))

(lambda extract-?vim-fn-name [x]
"Extract \"foobar\" from multi-symbol `vim.fn.foobar`, or return `nil`.
@param x any
@return string|nil"
(when (multi-sym? x)
(let [(fn-name pos) (-> (->str x) (: :gsub "^vim%.fn%." ""))]
(when (< 0 pos)
fn-name))))
(let [name (->str x)
pat-vim-fn "^vim%.fn%.(%S+)$"]
(name:match pat-vim-fn)))

(lambda deprecate [deprecated alternative version compatible]
"Return a wrapper function, which returns `compatible`, about to notify
Expand Down Expand Up @@ -274,15 +294,23 @@
(set extra-opts.command callback)
(or extra-opts.<callback> extra-opts.cb ;
(sym? callback) ;
(anonymous-function? callback))
(anonymous-function? callback) ;
(quoted? callback))
;; Note: Ignore the possibility to set Vimscript function to callback
;; in string; however, convert `vim.fn.foobar` into "foobar" to set
;; to "callback" key because functions written in Vim script are
;; rarely supposed to expect the table from `nvim_create_autocmd` for
;; its first arg.
(set extra-opts.callback
(or (extract-?vim-fn-name callback) ;
callback))
(let [cb (->unquoted callback)]
(set extra-opts.callback
;; Note: Either vim.fn.foobar or `vim.fn.foobar should be
;; "foobar" set to "callback" key.
(or (extract-?vim-fn-name cb) ;
(if (sym? callback)
(deprecate "callback function in symbol for `augroup!`, `autocmd!`, ..."
"quote \"`\" like `foobar" :v0.6.0
callback)
cb))))
(set extra-opts.command callback))
(let [api-opts (merge-api-opts (autocmd/->compatible-opts! extra-opts)
?api-opts)]
Expand Down Expand Up @@ -406,15 +434,21 @@
(if (or extra-opts.<command> extra-opts.ex) raw-rhs
(or extra-opts.<callback> extra-opts.cb ;
(sym? raw-rhs) ;
(anonymous-function? raw-rhs)) ;
(anonymous-function? raw-rhs) ;
(quoted? raw-rhs))
(do
;; Hack: `->compatible-opts` must remove
;; `cb`/`<callback>` key instead, but it doesn't at
;; present. It should be reported to Fennel repository,
;; but no idea how to reproduce it in minimal codes.
(set extra-opts.cb nil)
(set extra-opts.<callback> nil)
(set extra-opts.callback raw-rhs)
(set extra-opts.callback
(if (sym? raw-rhs)
(deprecate "callback function in symbol for `map!`"
"quote \"`\" like `foobar" :v0.6.0
raw-rhs)
(->unquoted raw-rhs)))
"") ;
;; Otherwise, Normal mode commands.
raw-rhs))
Expand Down Expand Up @@ -866,11 +900,12 @@
:<buffer>
:register
:keepscript]))
[extra-opts name command ?api-opts] (if-not ?extra-opts
[{} a1 a2 ?a3]
(sequence? a1)
[?extra-opts a2 ?a3 ?a4]
[?extra-opts a1 ?a3 ?a4])
[extra-opts name raw-command ?api-opts] (if-not ?extra-opts
[{} a1 a2 ?a3]
(sequence? a1)
[?extra-opts a2 ?a3 ?a4]
[?extra-opts a1 ?a3 ?a4])
command (->unquoted raw-command)
?bufnr (if extra-opts.<buffer> 0 extra-opts.buffer)
api-opts (merge-api-opts (command/->compatible-opts! extra-opts)
?api-opts)]
Expand Down
66 changes: 62 additions & 4 deletions tests/spec/autocmd_spec.fnl
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
(import-macros {: augroup! : augroup+ : au! : autocmd!} :nvim-laurel.macros)

(macro macro-callback []
`#:macro-callback)

(macro macro-command []
:macro-command)

(local default-augroup :default-test-augroup)
(local default-event :BufRead)
(local default-callback #:default-callback)
(local default-command :default-command)
(local default {:multi {:sym #:default.multi.sym}})

(lambda get-autocmds [?opts]
(let [opts (collect [k v (pairs (or ?opts {})) ;
Expand All @@ -16,6 +23,11 @@

(describe :autocmd
(fn []
(setup (fn []
(vim.cmd "function g:Test() abort
endfunction")))
(teardown (fn []
(vim.cmd "delfunction g:Test")))
(before_each (fn []
(augroup! default-augroup)
(let [aus (get-autocmds)]
Expand All @@ -36,7 +48,57 @@
#(let [id (augroup! default-augroup)]
(assert.is.same id (augroup+ default-augroup))))))
(describe :au!/autocmd!
(it "sets callback via macro with quote"
(fn []
(autocmd! default-augroup default-event [:pat] `(macro-callback))
(let [au (get-first-autocmd {:pattern :pat})]
(assert.is_not_nil au.callback))))
(it "set command in macro with no args"
(fn []
(autocmd! default-augroup default-event [:pat] (macro-command))
(let [au (get-first-autocmd {:pattern :pat})]
(assert.is_same :macro-command au.command))))
(it "set command in macro with some args"
(fn []
(autocmd! default-augroup default-event [:pat]
(macro-command :foo :bar))
(let [au (get-first-autocmd {:pattern :pat})]
(assert.is_same :macro-command au.command))))
(fn []
(it "sets callback function with quoted symbol"
#(do
(autocmd! default-augroup default-event [:pat] `default-callback)
(assert.is_same default-callback
(. (get-first-autocmd {:pattern :pat}) :callback))))
(it "sets callback function with quoted multi-symbol"
#(let [desc :multi.sym]
(autocmd! default-augroup default-event [:pat] `default.multi.sym
{: desc})
;; FIXME: In vusted, callback is unexpectedly set to a string
;; "<vim function: default.multi.sym>"; it must be the same as
;; `default.multi.sym`.
(assert.is_same desc (. (get-first-autocmd {:pattern :pat}) :desc))))
(it "sets callback function with quoted list"
#(let [desc :list]
(autocmd! default-augroup default-event [:pat]
`(default-callback :foo :bar) {: desc})
(let [au (get-first-autocmd {:pattern :pat})]
(assert.is_same desc au.desc))))
(it "set `vim.fn.Test in string \"Test\""
(fn []
(autocmd! default-augroup default-event [:pat] `vim.fn.Test)
(let [au (get-first-autocmd {:pattern :pat})]
(assert.is_same "<vim function: Test>" au.callback))))
(it "set `(vim.fn.Test) to callback as #(vim.fn.Test)"
(fn []
(autocmd! default-augroup default-event [:pat] `(vim.fn.Test))
(let [au (get-first-autocmd {:pattern :pat})]
(assert.is_not_same "<vim function: Test>" au.callback))))
(it "set #(vim.fn.Test) to callback without modification"
(fn []
(autocmd! default-augroup default-event [:pat] #(vim.fn.Test))
(let [au (get-first-autocmd {:pattern :pat})]
(assert.is_not_same "<vim function: Test>" au.callback))))
(describe "detects 2 args:"
(fn []
(it "sequence pattern and string callback"
Expand Down Expand Up @@ -138,10 +200,6 @@
(assert.is.same "<vim function: foobar>" autocmd2.callback))))
(it "sets vim.fn.Test to callback in string"
(fn []
(vim.cmd "
function! g:Test() abort
endfunction
")
(assert.has_no.errors #(autocmd! default-augroup default-event
vim.fn.Test))
(let [[autocmd] (get-autocmds)]
Expand Down
Loading

0 comments on commit 98f0dcd

Please sign in to comment.