├── .gitignore ├── .gitattributes ├── lua └── nvim-js-actions │ ├── init.lua │ ├── js-arrow-fn.test.ts │ └── js-arrow-fn.lua ├── doc └── nvim-js-actions.txt ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.mp4 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.mp4 filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /lua/nvim-js-actions/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.js_arrow_fn = require('nvim-js-actions.js-arrow-fn') 4 | 5 | return M 6 | -------------------------------------------------------------------------------- /lua/nvim-js-actions/js-arrow-fn.test.ts: -------------------------------------------------------------------------------- 1 | const foo = bar => "baz" 2 | const foo1 = (bar) => "baz" 3 | const foo2 = (bar: number) => "baz" 4 | const foo3 = (bar, fizz: string, buzz) => { 5 | return "baz" 6 | } 7 | console.log((bar => "baz")()) 8 | console.log(((bar) => "baz")()) 9 | console.log(((bar: number) => "baz")(5)) 10 | -------------------------------------------------------------------------------- /doc/nvim-js-actions.txt: -------------------------------------------------------------------------------- 1 | # `nvim-treesitter`-based actions on JavaScript code 2 | 3 | ## with `lazy.nvim` 4 | ```lua 5 | local js_filetypes = { 6 | "javascript", 7 | "javascriptreact", 8 | "typescript", 9 | "typescriptreact", 10 | } 11 | 12 | return { 13 | { 14 | "llllvvuu/nvim-js-actions", 15 | ft = js_filetypes, 16 | dependencies = { "nvim-treesitter/nvim-treesitter" }, 17 | init = function() 18 | vim.api.nvim_create_autocmd( 19 | "FileType", 20 | { 21 | pattern = js_filetypes, 22 | command = "nnoremap ta " .. 23 | ":lua require('nvim-js-actions').js_arrow_fn.toggle()" 24 | -- can also do `require('nvim-js-actions/js-arrow-fn').toggle()` 25 | } 26 | ) 27 | end 28 | } 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `nvim-treesitter`-based actions on JavaScript code 2 | 3 | I would have considered as `null-ls` code actions but unfortunately that project is retiring. 4 | 5 | ## with `lazy.nvim` 6 | ```lua 7 | local js_filetypes = { 8 | "javascript", 9 | "javascriptreact", 10 | "typescript", 11 | "typescriptreact", 12 | } 13 | 14 | return { 15 | { 16 | "llllvvuu/nvim-js-actions", 17 | ft = js_filetypes, 18 | dependencies = { "nvim-treesitter/nvim-treesitter" }, 19 | init = function() 20 | vim.api.nvim_create_autocmd( 21 | "FileType", 22 | { 23 | pattern = js_filetypes, 24 | command = "nnoremap ta " .. 25 | ":lua require('nvim-js-actions').js_arrow_fn.toggle()" 26 | -- can also do `require('nvim-js-actions/js-arrow-fn').toggle()` 27 | } 28 | ) 29 | end 30 | } 31 | } 32 | ``` 33 | 34 | ## `js_arrow_fn` 35 | Toggles between `function` and `=>` syntax. 36 | 37 | https://github.com/llllvvuu/nvim-js-actions/assets/5601392/69d436f2-4801-4521-8e58-3e22810914ed 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 llllvvuu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lua/nvim-js-actions/js-arrow-fn.lua: -------------------------------------------------------------------------------- 1 | local ts_utils = require('nvim-treesitter.ts_utils') 2 | 3 | local M = {} 4 | 5 | local function ensure_parentheses(str) 6 | if not string.find(str, "^%s*%(") then 7 | return "(" .. str .. ")" 8 | end 9 | return str 10 | end 11 | 12 | local function toggle_arrow_vs_function() 13 | local node = ts_utils.get_node_at_cursor() 14 | 15 | -- 'function' means non-arrow anonymous function 16 | while node ~= nil 17 | and node:type() ~= 'function_declaration' 18 | and node:type() ~= 'function' 19 | and node:type() ~= 'arrow_function' do 20 | node = node:parent() 21 | end 22 | 23 | if node == nil then 24 | vim.notify("Cursor is not in a function.") 25 | return 26 | end 27 | 28 | local buf = vim.api.nvim_get_current_buf() 29 | local start_row, start_col, end_row, end_col = node:range() 30 | local children = ts_utils.get_named_children(node) 31 | 32 | -- Get the current cursor position, to restore after the function is replaced 33 | local win = vim.api.nvim_get_current_win() 34 | local cursor_pos = vim.api.nvim_win_get_cursor(win) 35 | 36 | -- Extract the function name, and body, and rest 37 | local func_name, body 38 | local rest = {} 39 | 40 | if node:type() == 'function_declaration' then 41 | func_name = vim.treesitter.get_node_text(children[1], buf) 42 | body = vim.treesitter.get_node_text(children[#children], buf) 43 | 44 | for i=2, #children-1 do 45 | table.insert(rest, vim.treesitter.get_node_text(children[i], buf)) 46 | end 47 | else 48 | local parent = node:parent() 49 | if parent:type() == 'variable_declarator' then 50 | start_row, start_col, _, _ = parent:parent():range() 51 | func_name = vim.treesitter.get_node_text(parent:named_child(0), buf) 52 | else 53 | func_name = '' 54 | end 55 | 56 | body = vim.treesitter.get_node_text(children[#children], buf) 57 | if children[#children]:type() ~= 'statement_block' then 58 | body = "{\nreturn " .. body .. "\n}" 59 | end 60 | 61 | for i=1, #children-1 do 62 | local child_text = vim.treesitter.get_node_text(children[i], buf) 63 | -- parameter without parentheses needs to get parenthesized 64 | if children[i]:type() == 'identifier' then 65 | child_text = ensure_parentheses(child_text) 66 | end 67 | table.insert(rest, child_text) 68 | end 69 | end 70 | 71 | local rest_str = table.concat(rest) 72 | 73 | -- Simplify return statement if possible when converting to arrow 74 | if node:type() ~= 'arrow_function' then 75 | local body_node = children[#children] 76 | if body_node:type() == 'statement_block' then 77 | local return_stmt_node = body_node:named_child(0) 78 | if return_stmt_node and return_stmt_node:type() == 'return_statement' then 79 | body = vim.treesitter.get_node_text( 80 | return_stmt_node:named_child(0), buf) 81 | end 82 | end 83 | end 84 | 85 | -- Construct the new function 86 | local new_func 87 | if node:type() == 'function_declaration' then 88 | new_func = "const " .. func_name .. " = " .. rest_str .. " => " .. body 89 | elseif node:type() == 'function' then 90 | new_func = rest_str .. " => " .. body 91 | else 92 | new_func = "function " .. func_name .. rest_str .. " " .. body 93 | end 94 | 95 | local new_func_lines = vim.split(new_func, "\n") 96 | 97 | -- Recover the line content from before and after the function 98 | local start_line = vim.api.nvim_buf_get_lines( 99 | 0, 100 | start_row, 101 | start_row + 1, 102 | false 103 | )[1] 104 | local end_line = vim.api.nvim_buf_get_lines(0, end_row, end_row + 1, false)[1] 105 | 106 | local prefix = start_line:sub(1, start_col) 107 | local suffix = end_line:sub(end_col + 1) 108 | 109 | new_func_lines[1] = prefix .. new_func_lines[1] 110 | new_func_lines[#new_func_lines] = new_func_lines[#new_func_lines] .. suffix 111 | 112 | -- Replace the old function with the new function 113 | vim.api.nvim_buf_set_lines(0, start_row, end_row + 1, false, new_func_lines) 114 | 115 | local new_end_row = start_row + #new_func_lines 116 | local new_end_row_text = vim.api.nvim_buf_get_lines( 117 | 0, 118 | new_end_row-1, 119 | new_end_row, 120 | false 121 | )[1] 122 | 123 | -- Fix the formatting 124 | vim.cmd('normal! ' .. start_row+1 .. 'G=' .. new_end_row .. 'G') 125 | vim.lsp.buf.format({ 126 | range = { 127 | ["start"] = { start_row + 1, start_col }, 128 | ["end"] = { start_row + #new_func_lines, #new_end_row_text }, 129 | } 130 | }) 131 | 132 | -- Restore cursor position 133 | vim.api.nvim_win_set_cursor(win, cursor_pos) 134 | end 135 | 136 | M.toggle = toggle_arrow_vs_function 137 | 138 | return M 139 | --------------------------------------------------------------------------------