├── .gitignore ├── LICENSE ├── README.md ├── dev └── init.lua ├── lua └── nvim-terminal │ ├── config.lua │ ├── init.lua │ ├── terminal.lua │ ├── util.lua │ └── window.lua └── resources └── gif └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 s1n7ax 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-terminal 2 | 3 | Terminal plugin to open/toggle the terminals in Neovim 4 | 5 | https://user-images.githubusercontent.com/18459807/129582749-2e732591-cb8d-4cb8-a427-9da0c79a621d.mp4 6 | 7 | ## Features 8 | 9 | * Toggle terminal window 10 | * Quick switching between multiple terminal buffers 11 | 12 | ## Install the plugin 13 | 14 | **packer** 15 | ```lua 16 | use { 17 | 's1n7ax/nvim-terminal', 18 | config = function() 19 | vim.o.hidden = true 20 | require('nvim-terminal').setup() 21 | end, 22 | } 23 | ``` 24 | 25 | ## Default Keymaps 26 | 27 | leader + ; - **Toggle open/close terminal** 28 | 29 | leader + 1 - **Open terminal 1** 30 | 31 | leader + 2 - **Open terminal 2** 32 | 33 | leader + 3 - **Open terminal 3** 34 | 35 | leader + 4 - **Open terminal 4** 36 | 37 | leader + 5 - **Open terminal 5** 38 | 39 | leader + + - **Increase window height** 40 | 41 | leader + - - **Decrease window height** 42 | 43 | leader + leader + + - **Increase window width** 44 | 45 | leader + leader + - - **Decrease window width** 46 | 47 | ## Configuration 48 | 49 | Simply pass the custom configuration to `setup` method 50 | 51 | ```lua 52 | -- following option will hide the buffer when it is closed instead of deleting 53 | -- the buffer. This is important to reuse the last terminal buffer 54 | -- IF the option is not set, plugin will open a new terminal every single time 55 | vim.o.hidden = true 56 | 57 | require('nvim-terminal').setup({ 58 | window = { 59 | -- Do `:h :botright` for more information 60 | -- NOTE: width or height may not be applied in some "pos" 61 | position = 'botright', 62 | 63 | -- Do `:h split` for more information 64 | split = 'sp', 65 | 66 | -- Width of the terminal 67 | width = 50, 68 | 69 | -- Height of the terminal 70 | height = 15, 71 | }, 72 | 73 | -- keymap to disable all the default keymaps 74 | disable_default_keymaps = false, 75 | 76 | -- keymap to toggle open and close terminal window 77 | toggle_keymap = ';', 78 | 79 | -- increase the window height by when you hit the keymap 80 | window_height_change_amount = 2, 81 | 82 | -- increase the window width by when you hit the keymap 83 | window_width_change_amount = 2, 84 | 85 | -- keymap to increase the window width 86 | increase_width_keymap = '+', 87 | 88 | -- keymap to decrease the window width 89 | decrease_width_keymap = '-', 90 | 91 | -- keymap to increase the window height 92 | increase_height_keymap = '+', 93 | 94 | -- keymap to decrease the window height 95 | decrease_height_keymap = '-', 96 | 97 | terminals = { 98 | -- keymaps to open nth terminal 99 | {keymap = '1'}, 100 | {keymap = '2'}, 101 | {keymap = '3'}, 102 | {keymap = '4'}, 103 | {keymap = '5'}, 104 | }, 105 | }) 106 | ``` 107 | 108 | ## Add Keymaps Manually 109 | 110 | `nvim-terminal` adds a global variable called `NTGlobal`. When you call 111 | `require('nvim-terminal').setup()` it adds `terminal` and `window` properties to 112 | `NTGlobal` 113 | 114 | ```lua 115 | vim.api.nvim_set_keymap('n', 't', ':lua NTGlobal["terminal"]:toggle()', silent) 116 | vim.api.nvim_set_keymap('n', '1', ':lua NTGlobal["terminal"]:open(1)', silent) 117 | vim.api.nvim_set_keymap('n', '+', ':lua NTGlobal["window"]:change_height(2)', silent) 118 | vim.api.nvim_set_keymap('n', '-', ':lua NTGlobal["window"]:change_height(-2)', silent) 119 | ``` 120 | 121 | ## PRO MODE 122 | 123 | ### Default Terminal 124 | 125 | ```lua 126 | terminal = require('nvim-terminal').DefaultTerminal; 127 | 128 | local silent = { silent = true } 129 | 130 | vim.api.nvim_set_keymap('n', 't', ':lua terminal:toggle()', silent) 131 | vim.api.nvim_set_keymap('n', '1', ':lua terminal:open(1)', silent) 132 | vim.api.nvim_set_keymap('n', '2', ':lua terminal:open(2)', silent) 133 | vim.api.nvim_set_keymap('n', '3', ':lua terminal:open(3)', silent) 134 | ``` 135 | 136 | ### Customized Window 137 | 138 | ```lua 139 | local Terminal = require('nvim-terminal.terminal') 140 | local Window = require('nvim-terminal.window') 141 | 142 | local window = Window:new({ 143 | position = 'botright', 144 | split = 'sp', 145 | width = 50, 146 | height = 15 147 | }) 148 | 149 | terminal = Terminal:new(window) 150 | ``` 151 | -------------------------------------------------------------------------------- /dev/init.lua: -------------------------------------------------------------------------------- 1 | -- plugin name will be used to reload the loaded modules 2 | local package_name = 'nvim-terminal' 3 | 4 | -- add the escape character to special characters 5 | local escape_pattern = function(text) return text:gsub('([^%w])', '%%%1') end 6 | 7 | -- unload loaded modules by the matching text 8 | local unload_packages = function() 9 | local esc_package_name = escape_pattern(package_name) 10 | 11 | for module_name, _ in pairs(package.loaded) do 12 | if string.find(module_name, esc_package_name) then 13 | package.loaded[module_name] = nil 14 | end 15 | end 16 | end 17 | 18 | -- executes the run method in the package 19 | local run_action = function() 20 | require('nvim-terminal').setup() 21 | 22 | end 23 | 24 | -- unload and run the function from the package 25 | function Reload_and_run() 26 | unload_packages() 27 | run_action() 28 | end 29 | 30 | local set_keymap = vim.api.nvim_set_keymap 31 | 32 | set_keymap('n', ',r', 'luafile dev/init.lua', {}) 33 | set_keymap('n', ',w', 'lua Reload_and_run()', {}) 34 | -------------------------------------------------------------------------------- /lua/nvim-terminal/config.lua: -------------------------------------------------------------------------------- 1 | local config = { 2 | window = { 3 | -- Do `:h :botright` for more information 4 | position = 'botright', 5 | 6 | -- Do `:h split` for more information 7 | split = 'sp', 8 | 9 | -- Width of the terminal 10 | width = 50, 11 | 12 | -- Height of the terminal 13 | height = 15, 14 | }, 15 | 16 | -- keymap to disablesb all the default keymaps 17 | disable_default_keymaps = false, 18 | 19 | -- keymap to toggle open and close terminal window 20 | toggle_keymap = ';', 21 | 22 | -- increase the window width by when you hit the keymap 23 | window_height_change_amount = 2, 24 | 25 | -- increase the window height by when you hit the keymap 26 | window_width_change_amount = 2, 27 | 28 | -- keymap to increase the window width 29 | increase_width_keymap = '+', 30 | 31 | -- keymap to decrease the window width 32 | decrease_width_keymap = '-', 33 | 34 | -- keymap to increase the window height 35 | increase_height_keymap = '+', 36 | 37 | -- keymap to decrease the window height 38 | decrease_height_keymap = '-', 39 | 40 | terminals = { 41 | -- keymaps to open nth terminal 42 | {keymap = '1'}, 43 | {keymap = '2'}, 44 | {keymap = '3'}, 45 | {keymap = '4'}, 46 | {keymap = '5'}, 47 | }, 48 | } 49 | 50 | return config 51 | -------------------------------------------------------------------------------- /lua/nvim-terminal/init.lua: -------------------------------------------------------------------------------- 1 | local config = require("nvim-terminal.config") 2 | local Util = require("nvim-terminal.util") 3 | local Terminal = require("nvim-terminal.terminal") 4 | local Window = require("nvim-terminal.window") 5 | local DefaultTerminal = Terminal:new(Window:new()) 6 | local S = Util.String 7 | 8 | NTGlobal = {} 9 | 10 | local setup = function(opts) 11 | config = Util.Lua.merge_tables(config, opts or {}) 12 | 13 | if config.terminals == nil then 14 | return 15 | end 16 | 17 | local window = Window:new(config.window) 18 | local terminal = Terminal:new(window) 19 | 20 | NTGlobal["terminal"] = terminal 21 | NTGlobal["window"] = window 22 | 23 | if not config.disable_default_keymaps then 24 | -- setting toggle keymap 25 | if S.is_not_empty(config.toggle_keymap) then 26 | vim.keymap.set("n", config.toggle_keymap, function() 27 | NTGlobal["terminal"]:toggle() 28 | end, { silent = true }) 29 | end 30 | 31 | -- setting window width keymap 32 | if S.is_not_empty(config.increase_width_keymap) then 33 | vim.keymap.set("n", config.increase_width_keymap, function() 34 | NTGlobal["window"]:change_width(config.window_width_change_amount) 35 | end, { silent = true }) 36 | end 37 | 38 | -- setting window width keymap 39 | if S.is_not_empty(config.decrease_width_keymap) then 40 | vim.keymap.set("n", config.decrease_width_keymap, function() 41 | NTGlobal["window"]:change_width(-config.window_width_change_amount) 42 | end, { silent = true }) 43 | end 44 | 45 | -- setting window height keymap 46 | if S.is_not_empty(config.increase_height_keymap) then 47 | vim.keymap.set("n", config.increase_height_keymap, function() 48 | NTGlobal["window"]:change_height(config.window_height_change_amount) 49 | end, { silent = true }) 50 | end 51 | 52 | -- setting window height keymap 53 | if S.is_not_empty(config.decrease_height_keymap) then 54 | vim.keymap.set("n", config.decrease_height_keymap, function() 55 | NTGlobal["window"]:change_height(-config.window_height_change_amount) 56 | end, { silent = true }) 57 | end 58 | 59 | for index, term_conf in ipairs(config.terminals) do 60 | -- setting terminal keymap 61 | vim.keymap.set("n", term_conf.keymap, function() 62 | NTGlobal["terminal"]:open(index) 63 | end, { silent = true }) 64 | end 65 | end 66 | end 67 | 68 | return { 69 | Terminal = Terminal, 70 | Window = Window, 71 | DefaultTerminal = DefaultTerminal, 72 | setup = setup, 73 | } 74 | -------------------------------------------------------------------------------- /lua/nvim-terminal/terminal.lua: -------------------------------------------------------------------------------- 1 | local Window = require('nvim-terminal.window') 2 | 3 | local v = vim.api 4 | local cmd = vim.cmd 5 | 6 | local Terminal = {bufs = {}, last_winid = nil, last_term = nil} 7 | 8 | function Terminal:new(window, opt) 9 | self.window = window or Window:new() 10 | return self 11 | end 12 | 13 | function Terminal:init() 14 | error('There are some breaking changes!') 15 | error( 16 | 'Please check new configuration at https://github.com/s1n7ax/nvim-terminal') 17 | end 18 | 19 | function Terminal:open(term_number) 20 | term_number = term_number or 1 21 | 22 | local create_win = not self.window:is_valid() 23 | -- create buffer if it does not exist by the given term_number or the stored 24 | -- buffer number is no longer valid 25 | local create_buf = self.bufs[term_number] == nil or 26 | not v.nvim_buf_is_valid(self.bufs[term_number]) 27 | 28 | -- window and buffer does not exist 29 | if create_win and create_buf then 30 | self.last_winid = v.nvim_get_current_win() 31 | self.window:create_term() 32 | self.bufs[term_number] = self.window:get_bufno() 33 | 34 | -- window does not exist but buffer does 35 | elseif create_win then 36 | self.last_winid = v.nvim_get_current_win() 37 | self.window:create(self.bufs[term_number]) 38 | 39 | -- buffer does not exist but window does 40 | elseif create_buf then 41 | self.window:focus() 42 | cmd(':terminal') 43 | self.bufs[term_number] = self.window:get_bufno() 44 | 45 | -- buffer and window exist 46 | else 47 | local curr_term_buf = self.bufs[term_number] 48 | local last_term_buf = self.bufs[self.last_term] 49 | 50 | if curr_term_buf ~= last_term_buf then 51 | self.window:set_buf(curr_term_buf) 52 | end 53 | end 54 | 55 | self.last_term = term_number 56 | end 57 | 58 | function Terminal:close() 59 | local current_winid = v.nvim_get_current_win() 60 | 61 | if self.window:is_valid() then 62 | self.window:close() 63 | 64 | if current_winid == self.window.winid then 65 | v.nvim_set_current_win(self.last_winid) 66 | end 67 | end 68 | end 69 | 70 | function Terminal:toggle() 71 | self.last_term = self.last_term and self.last_term or 1 72 | 73 | local opened = self.window:is_valid() 74 | 75 | if opened then 76 | self:close() 77 | else 78 | self:open(self.last_term) 79 | end 80 | 81 | end 82 | 83 | return Terminal 84 | -------------------------------------------------------------------------------- /lua/nvim-terminal/util.lua: -------------------------------------------------------------------------------- 1 | local Lua = {} 2 | 3 | -- Merge content of two table and returns a new table 4 | function Lua.merge_tables(t1, t2) 5 | for k, v in pairs(t2) do 6 | if (type(v) == 'table') and (type(t1[k] or false) == 'table') then 7 | Lua.merge_tables(t1[k], t2[k]) 8 | else 9 | t1[k] = v 10 | end 11 | end 12 | 13 | return t1 14 | end 15 | 16 | local String = {} 17 | 18 | function String.is_not_empty(str) return str ~= nil and str ~= '' or false end 19 | 20 | return {Lua = Lua, String = String} 21 | -------------------------------------------------------------------------------- /lua/nvim-terminal/window.lua: -------------------------------------------------------------------------------- 1 | local v = vim.api 2 | local cmd = vim.cmd 3 | local fn = vim.fn 4 | 5 | local Window = {} 6 | 7 | function Window:new(opt) 8 | opt = opt and opt or {} 9 | 10 | self.pos = opt.pos or opt.position or 'botright' 11 | self.split = opt.split or 'sp' 12 | self.width = opt.width or nil 13 | self.height = opt.height or nil 14 | 15 | return self 16 | end 17 | 18 | -- Opens new window bottom of tab 19 | -- @return { number } window id 20 | function Window:create(bufnr) 21 | local cmd_format = '%s %s +buffer\\ %d' 22 | cmd(cmd_format:format(self.pos, self.split, bufnr)) 23 | 24 | self.winid = fn.win_getid() 25 | 26 | self:update_size() 27 | 28 | return self.winid 29 | end 30 | 31 | -- Opens new terminal window bottom of tab 32 | -- @return { number } window number 33 | function Window:create_term() 34 | local cmd_format = '%s new +term' 35 | cmd(cmd_format:format(self.pos)) 36 | 37 | self.winid = fn.win_getid() 38 | 39 | self:update_size() 40 | 41 | return self.winid 42 | end 43 | 44 | -- Set window width to self.width 45 | function Window:update_size() 46 | if self.width ~= nil then v.nvim_win_set_width(self.winid, self.width) end 47 | 48 | if self.height ~= nil then v.nvim_win_set_height(self.winid, self.height) end 49 | end 50 | 51 | function Window:get_size() 52 | local width = v.nvim_win_get_width(self.winid) 53 | local height = v.nvim_win_get_height(self.winid) 54 | 55 | return width, height 56 | end 57 | 58 | -- close the window 59 | function Window:close(winid) 60 | if self:is_valid() then v.nvim_win_close(self.winid, false) end 61 | end 62 | 63 | -- Returns the validity of the window 64 | -- @return { boolean } window is valid or not 65 | function Window:is_valid() 66 | if (self.winid == nil) then return false end 67 | 68 | return v.nvim_win_is_valid(self.winid) 69 | end 70 | 71 | function Window:set_buf(bufno) return v.nvim_win_set_buf(self.winid, bufno) end 72 | 73 | function Window:focus() v.nvim_set_current_win(self.winid) end 74 | 75 | -- Returns the buffer number 76 | -- @return { number } buffer number 77 | function Window:get_bufno() 78 | if self:is_valid() then return v.nvim_win_get_buf(self.winid) end 79 | end 80 | 81 | -- Increase window height 82 | function Window:change_height(by) 83 | local _, height = self:get_size() 84 | self.height = height + by 85 | self:update_size() 86 | end 87 | 88 | -- Increase window height 89 | function Window:change_width(by) 90 | local width, _ = self:get_size() 91 | self.width = width + by 92 | self:update_size() 93 | end 94 | 95 | return Window 96 | -------------------------------------------------------------------------------- /resources/gif/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s1n7ax/nvim-terminal/e058de4b8029d7605b17275f30f83be8f8df5f62/resources/gif/demo.gif --------------------------------------------------------------------------------