1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
--[[ 50-terminal.lua — terminal buffers: follow output and preserve terminal mode.
Opens terminals in insert mode and, while "follow" mode is on, pins the view to the latest output by
jumping to the bottom (G) whenever terminal mode or the terminal window is left.
Follow mode is per-buffer, shown as `+T` in the statusline (50-ui.lua) and toggled with `G` or
:TerminalFollow.
A click inside a terminal is swallowed so it stays in terminal mode, while a click on another window
passes through to focus it.
User commands:
TerminalFollow: toggle following terminal output
Keymaps:
`<Esc>` exit terminal mode (terminal mode)
`G` toggle following terminal output (in a terminal buffer)
]]
----------------------------------------------------------------------------------------------------
-- Callbacks ---------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
--[[ Jump to the bottom (G) when follow mode is on, so the view tracks the latest terminal output.
fzf-lua runs fzf in its own terminal (`filetype=fzf`); a `normal! G` there drops it out of terminal
mode mid-picker so keys stop reaching fzf, so those buffers are skipped.
Terminal mode is skipped too: `normal` cannot run there ("Can't re-enter normal mode from terminal
mode" — e.g. the WinLeave fired by the buffer wipe when the shell exits), and terminal mode
already follows the output. ]]
local function terminal_follow_to_bottom()
if
vim.b.terminal_follow
and vim.bo.filetype ~= "fzf"
and vim.api.nvim_get_mode().mode ~= "t"
then
vim.cmd.normal({ "G", bang = true })
end
end
--[[ Toggle "follow" mode for the current terminal, shown as `+T` in the statusline (50-ui.lua).
While on, leaving terminal mode or the terminal window jumps to the bottom (G) so the view tracks
the latest output. ]]
local function toggle_terminal_follow()
if vim.bo.buftype ~= "terminal" then
vim.notify("TerminalFollow only applies to terminal buffers", vim.log.levels.WARN)
return
end
vim.b.terminal_follow = not vim.b.terminal_follow
terminal_follow_to_bottom() -- Snap to the bottom right away when enabling
end
-- Enter terminal mode and follow output by default when a terminal opens.
local function terminal_open()
vim.b.terminal_follow = true
-- Buffer-local so it doesn't shadow normal-mode G (jump to bottom) elsewhere
vim.keymap.set("n", "G", "<Cmd>TerminalFollow<CR>", {
buffer = true,
desc = "Toggle following terminal output",
})
vim.cmd.startinsert()
end
--[[ Keep following after entering a terminal window.
Defer so a mouse click's cursor placement runs first, then override it with G. ]]
local function terminal_follow_on_enter()
if vim.b.terminal_follow then
vim.schedule(terminal_follow_to_bottom)
end
end
-- Keep following after a mouse click inside a terminal window.
-- Ignore drags, which enter visual mode.
local function terminal_follow_on_click()
if vim.fn.mode() == "n" then
vim.schedule(terminal_follow_to_bottom)
end
return "<LeftRelease>"
end
--[[ In terminal mode, feed no keys for a click inside the terminal so it stays in terminal mode.
A click on another window is let through so that window gets focus. ]]
local function terminal_click()
return vim.fn.getmousepos().winid == vim.api.nvim_get_current_win() and "" or "<LeftMouse>"
end
----------------------------------------------------------------------------------------------------
-- Declarations ------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
vim.api.nvim_create_user_command(
"TerminalFollow",
toggle_terminal_follow,
{ desc = "Toggle following terminal output" }
)
-- stylua: ignore start
vim.keymap.set("t", "<Esc>", "<C-\\><C-n>", { desc = "Exit terminal mode" })
vim.keymap.set("t", "<LeftMouse>", terminal_click, { expr = true, desc = "Ignore click inside terminal, focus other window otherwise" })
vim.keymap.set("n", "<LeftRelease>", terminal_follow_on_click, { expr = true, desc = "Keep following terminal output on click" })
-- stylua: ignore end
local help = require("help")
help.register("General", {
maps = {
{ "<Esc>", "Exit terminal mode", "t" },
{ "<C-[>", "Send Esc to terminal" },
{ "G", "Toggle following terminal output" },
},
commands = { { "TerminalFollow", "Toggle following terminal output" } },
})
vim.api.nvim_create_autocmd("TermOpen", {
desc = "Follow terminal output by default",
group = vim.g.dotfiles_augroup,
callback = terminal_open,
})
vim.api.nvim_create_autocmd({ "TermLeave", "WinLeave" }, {
desc = "Follow terminal output",
group = vim.g.dotfiles_augroup,
callback = terminal_follow_to_bottom,
})
vim.api.nvim_create_autocmd("WinEnter", {
desc = "Follow terminal output",
group = vim.g.dotfiles_augroup,
callback = terminal_follow_on_enter,
})
|