Comments (43)
I think it works as expected. Time to make a PR. 😸
from cmake-tools.nvim.
Primarily these options:
local multiple_terminals_per_tab = false -- Prefer to keep false, since we will not close it
local keep_single_terminal_per_tab = true -- Prefer true if you want to keep only one per tab
local keep_terminal_in_tab_static = true -- Prefer if you want to use a single static instance
local display_single_window_across_instance = false -- Prefer to keep this false
local vert_split = true
from cmake-tools.nvim.
Finally!
@Civitasv, check this out:
Spwanning a child process does the trick... (since we can get list of child process list and compare if any child processes are active)
Including multiple processes...
After both child processes exit:
So, when there are no child processes left, then we can issue commands to the terminal buffer.
No need for exit code, as output will be directly displayed in the window buffer.
Killing the child process will also be easier, without losing all the previous output.
from cmake-tools.nvim.
This is a translation from this video: https://youtu.be/NBGD7GoqafY
Plus some window handling
from cmake-tools.nvim.
Interesting! I will try to test it today.
from cmake-tools.nvim.
Can you describe more about how should I test it? Especially where should I place this chunk of codes in?
from cmake-tools.nvim.
@Civitasv, I fixed it! (Just changed my approach... :D)
The following is a way to handle a single interactive terminal buffer (1 for cmake build, clean, generate and, cleanrebuild)
In the main PR, How about:
- Each executable can have a separate terminal instance to run it, with their own args as mentioned by @hfn92 in #53.
- Each debug executable can have a separate terminal instance to run it as well, with its own args.
PS: One weird thing I found out was that on window chan_send()
ing \\n
does not work(it acts as a new line). We have to send the enter command as: cmd .. string.char(13)
. This works both on WSl and windows, so I think we can stick the latter.
:source
this lua file: (kindly ignore the bad naming scheme... will fix it in the main PR)
Multi window, Multi tab handling for a single buffer
local term_idx -- This is the buffer number of term
local term_channel -- This is the channel number
local term_height = 10 -- Terminal height
local multiple_terminals_per_tab = false -- Prefer to keep false, since we will not close it
local keep_single_terminal_per_tab = true -- Prefer true if you want to keep only one per tab
local keep_terminal_in_tab_static = true -- Prefer if you want to use a single static instance
local display_single_window_across_instance = false -- Prefer to keep this false
local vert_split = true
local term_name = "my_term" -- name the channel... like CMakeRun
local positon = 'below' -- This si the location of inital term generation (as we are using vim api)
local cmd = "ls"
local is_startinsert = true
local previous_window = vim.api.nvim_get_current_win() -- This gives us an anchor... a starting point
---------------------------------------------------------
for i, buf_nr in ipairs(vim.api.nvim_list_bufs()) do
local name = vim.api.nvim_buf_get_name(buf_nr)
print(i .. " : " .. buf_nr .. ", name: " .. vim.api.nvim_buf_get_name(buf_nr) .. " type: " .. type(name))
print("buf exists: " .. tostring(string.match(name, term_name) == term_name))
local matched = string.match(name, term_name) == term_name
if matched then
term_idx = buf_nr
break
else
print("-> no test_buf")
end
print(" ")
end
-- Create terminal is not created!
local function is_buffer_hidden_in_current_tab(bufname)
local bufnr = vim.fn.bufnr(bufname)
for _, winid in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_buf(winid) == bufnr then
return false -- Buffer is shown in a split
end
end
return true -- Buffer is not shown in any split
end
function _G.getWindowsWithBuffer(bufname)
local windows = {}
for _, winid in ipairs(vim.api.nvim_list_wins()) do
local bufnr = vim.api.nvim_win_get_buf(winid)
local winbufname = vim.api.nvim_buf_get_name(bufnr)
if winbufname == bufname then
table.insert(windows, winid)
end
end
return windows
end
function _G.get_windows_displaying_my_buffer_across_all_tabs(bufname)
local windows = {}
for _, tabpage in ipairs(vim.api.nvim_list_tabpages()) do
vim.api.nvim_set_current_tabpage(tabpage)
for _, window in ipairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
local win_bufname = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(window))
if win_bufname:match(bufname) then
table.insert(windows, window)
end
end
end
return windows
end
function _G.is_buffer_displayed_in_current_tab(buf_name)
local current_tabpage = vim.api.nvim_get_current_tabpage()
for _, window in ipairs(vim.api.nvim_tabpage_list_wins(current_tabpage)) do
local win_bufnr = vim.api.nvim_win_get_buf(window)
local win_bufname = vim.api.nvim_buf_get_name(win_bufnr)
if win_bufname:match(buf_name) then
return true
end
end
return false
end
function _G.get_windows_for_buffer_in_current_tab(buf_name)
local windows = {}
local current_tabpage = vim.api.nvim_get_current_tabpage()
for _, window in ipairs(vim.api.nvim_tabpage_list_wins(current_tabpage)) do
local win_bufnr = vim.api.nvim_win_get_buf(window)
local win_bufname = vim.api.nvim_buf_get_name(win_bufnr)
if win_bufname:match(buf_name) then
table.insert(windows, window)
end
end
return windows
end
function _G.get_terminal_buffer_number_and_channel_number(name)
local bufinfo = vim.api.nvim_list_bufs()
for _, bufnr in ipairs(bufinfo) do
local buftype = vim.api.nvim_buf_get_option(bufnr, 'buftype')
local bufname = vim.api.nvim_buf_get_name(bufnr)
local filename = vim.fn.fnamemodify(bufname, ':t')
print("buftype: " .. buftype .. ", bufname: " .. bufname .. ", filename: " .. filename)
if buftype == 'terminal' and filename == name then
-- local term_chan = vim.api.nvim_buf_get_var(bufnr, '&channel')
local term_chan = vim.api.nvim_call_function('nvim_buf_get_var', { bufnr, 'terminal_job_id' })
return bufnr, term_chan
end
end
return nil, nil
end
function _G.getWindowNumberByBufferName(bufname)
local bufnr = vim.fn.bufnr(bufname)
for _, winid in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_buf(winid) == bufnr then
return vim.api.nvim_win_get_number(winid)
end
end
return nil -- Buffer is not open in any window
end
function _G.closeWindowsByBufferName(bufname)
local previous_window = vim.api.nvim_get_current_win()
for _, tabpage in ipairs(vim.api.nvim_list_tabpages()) do
vim.api.nvim_set_current_tabpage(tabpage)
for _, window in ipairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
local win_bufname = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(window))
if win_bufname:match(bufname) then
vim.api.nvim_set_current_win(window)
hide_window()
vim.api.nvim_set_current_win(previous_window)
return
end
end
end
vim.api.nvim_set_current_win(previous_window)
end
local function send_commands_to_terminal(buffer_preset_name, data, is_startinsert, focus_win_nr)
local bufnr, chan = get_terminal_buffer_number_and_channel_number(buffer_preset_name)
if chan > 0 then
vim.api.nvim_chan_send(chan, data .. string.char(13))
else
print("chan error!")
end
print("previous_window: ")
print(focus_win_nr)
vim.api.nvim_set_current_win(focus_win_nr)
if is_startinsert then
vim.cmd('startinsert')
end
end
local function set_new_window_for_buffer(split_win, buffer_idx, dimension)
if split_win == true then
vim.cmd('split')
end
vim.api.nvim_win_set_buf(0, buffer_idx)
if vert_split == true then
vim.api.nvim_win_set_height(0, dimension)
else
vim.api.nvim_win_set_width(0, dimension)
end
vim.cmd('setlocal noequalalways') -- Avoid resizing the split
return vim.api.nvim_get_current_win()
end
if term_idx then
print('terminal buffer exists!')
-- If user wants only a single buffer across all tabgroups, we skip the logic after this block
if display_single_window_across_instance == true then
print('single terminal across tabs!')
-- Close all windows displaying the terminal buffer
closeWindowsByBufferName(term_name)
-- Now split window and launch the task
local terminal_window = set_new_window_for_buffer(true, term_idx, term_height)
send_commands_to_terminal(term_name, cmd, is_startinsert, terminal_window)
--
-- Next, we check if the user wants single buffer per tab
elseif keep_single_terminal_per_tab == true then
if is_buffer_hidden_in_current_tab(term_name) == true then
print("terminal buffer is hidden!")
-- Since the buffer is hidden, we open it below the current split
local terminal_window = set_new_window_for_buffer(true, term_idx, term_height)
send_commands_to_terminal(term_name, cmd, is_startinsert, previous_window)
-- Else: Terminal is not hidden in current tabpage
else
print("terminal buffer is not hidden!.....")
-- Since, it is open in another window, we check how many windows display the buffer
-- Check if user wants to keep the terminal window in this buffer as static
if keep_terminal_in_tab_static == true then
print("running cmake task")
local terminal_window = get_windows_for_buffer_in_current_tab(term_name)[1]
send_commands_to_terminal(term_name, cmd, is_startinsert, previous_window)
else --- If user wants location of the single window tab as [[ NOT ]] static location
print("User wants to replace the loaction of the terminal in this current tab")
-- If terminal is displayed in a split in the current tab and close it, reopen below the current window
-- close all the windows/split that display our terminal and reopen
local windows_that_display_terminal_in_current_tab = get_windows_for_buffer_in_current_tab(term_name)
for _, win_nr in ipairs(windows_that_display_terminal_in_current_tab) do
vim.api.nvim_win_close(win_nr, { force = false }) -- do not force close
end
-- Now we re-open a single window below the current window and run the cmake task
local terminal_window = set_new_window_for_buffer(true, term_idx, term_height)
send_commands_to_terminal(term_name, cmd, is_startinsert, previous_window)
-- If the current window in already our terminal, then simply run the terminal cmake tasks,
-- else close terminal window in this current tab and reopen below current split
end
end
else -- Multiple buffers per tabpage
if term_idx == vim.api.nvim_get_current_buf() then
print("run task without splitting")
set_new_window_for_buffer(false, term_idx, term_height)
send_commands_to_terminal(term_name, cmd, is_startinsert, previous_window)
else
print('create window, load buffer and run task')
set_new_window_for_buffer(true, term_idx, term_height)
send_commands_to_terminal(term_name, cmd, is_startinsert, previous_window)
end
end
else
-- terminal does not exist, as we create the terminal first and immediately name it.
-- Next time when we get the terminal, we use it's name to get it
vim.cmd(':' .. positon .. ' :sp' .. ' | :term')
print("newly creating terminal")
term_channel = vim.api.nvim_buf_get_option(0, 'channel') -- get terminal channel. Next time, get channel number by name
vim.api.nvim_buf_set_name(0, term_name) -- immediately set name
set_new_window_for_buffer(false, 0, term_height)
send_commands_to_terminal(term_name, cmd, is_startinsert, previous_window)
end
local term_buffer_nr, term_channel_nr = get_terminal_buffer_number_and_channel_number('my_term')
print("term_buffer_nr: " .. term_buffer_nr .. ", term_channel_nr: " .. term_channel_nr)
from cmake-tools.nvim.
@Civitasv, you can set false
or true
for various settings and check it.
from cmake-tools.nvim.
Maybe you can make a PR, so I can get a test environment?
from cmake-tools.nvim.
Multi window, Multi tab handling for a single buffer
You can directly source this file without any dependencies.
from cmake-tools.nvim.
And so that we can keep the discussion in that PR.
from cmake-tools.nvim.
It is only for window/buffer handling. The key to make the terminals interactive is to not pass any cmd
after :term
in :term cmd
, and then use vim.api.nvim_chan_send(chan, data)
to execute commands, which also keeps the terminal persistent.
from cmake-tools.nvim.
Okay.
from cmake-tools.nvim.
I will start creating a PR for this in sometime, can you kindly test this lua file and let me know any changes?
All the print info is only for debugging.
from cmake-tools.nvim.
To see the effect, I can just source this file?
from cmake-tools.nvim.
from cmake-tools.nvim.
The change the options on the top of the file. you can disable the print statements
from cmake-tools.nvim.
To see the effect, I can just source this file?
Yes
from cmake-tools.nvim.
@Civitasv, I am wokring on this, but the current method of sending commands to job control (via cmakegenerate or cmakebuild, etc...) all check if there is an active job via a pid for the job.
Bu if it is to be executed in the terminal, is there a way to check of the terminal is ready for user input or has completed the previous command that was sent to the command with chansend
?
So as to not keep sending the same command multiple times to terminals by mistake?
from cmake-tools.nvim.
Is there something like mutex
lock in lua?
from cmake-tools.nvim.
No not natively. What's your suggestion for dealing with this?
something like mutex:
local lock = false
function acquireMutex()
while lock do
coroutine.yield()
end
lock = true
end
function releaseMutex()
lock = false
end
-- Usage example
acquireMutex()
-- Critical section
releaseMutex()
from cmake-tools.nvim.
Is it suitable to use coroutine? I think coroutine should use with async operation.
from cmake-tools.nvim.
I wonder if it is okay to just let it busy wait?
from cmake-tools.nvim.
How do we do that?
from cmake-tools.nvim.
Can we use this async feature of plenary?
from cmake-tools.nvim.
Can you an async give an example with this?
function utils.has_active_job()
if not utils.job or utils.job.is_shutdown then
return true
end
utils.error(
"A CMake task is already running: "
.. utils.job.command
.. " Stop it before trying to run a new CMake task."
)
return false
end
These checkers that prevent multiple requests are remaining.
from cmake-tools.nvim.
Maybe the structure will be like this:
local async = require("plenary.async")
local sender, receiver = async.control.channel.mpsc()
local function provider(n)
async.run(function()
sender.send(n)
end)
end
local function consumer()
local value = receiver.recv()
print("received:", value)
end
provider(10)
consumer()
provider(20)
consumer()
provider(30)
consumer()
provider(40)
consumer()
--[[ this will print 10, 20, 30, 40 ]]
from cmake-tools.nvim.
With a provider
call, there will be a consumer
call. Do you think it is okay?
from cmake-tools.nvim.
would this work with:
:lua vim.api.nvim_chan_send(vim.api.nvim_buf_get_var(vim.api.nvim_win_get_buf(vim.api.nvim_get_current_win()),'terminal_job_id'), 'ls\r')
after :term
?
from cmake-tools.nvim.
With a provider call, there will be a consumer call. Do you think it is okay?
I don't think whis will work. async does not seem to know the child process's command execution
from cmake-tools.nvim.
Can we get status of child process (:term
's) with the PID of the process?
I think that would be enough to tell if the terminal is ready for new inputs.
from cmake-tools.nvim.
http://neovim.io/doc/user/nvim_terminal_emulator.html#terminal-status
Use jobwait() to check if the terminal job has finished:
let running = jobwait([&channel], 0)[0] == -1
from cmake-tools.nvim.
Tried this. Since the terminal job is indefinite(as we resue the terminal for serial commands), the job keeps running.
from cmake-tools.nvim.
So as to not keep sending the same command multiple times to terminals by mistake?
Can you give me an example about this?
from cmake-tools.nvim.
Let us say that I hit (shortcut keybindings) CMakeGenereate
multiple times in sequence, by mistake.
Then I will be queueing commands to be sent to the terminal. (Tried this with Start Sleep -Seconds 10
in powershell, where I sent this twice. After the 1st 10 seconds, it ran the second one).
This is undesirable as the nvim_chan_send() will queue the commands and not disregard commands if terminal is busy.
from cmake-tools.nvim.
Can we get notified when command finished?
from cmake-tools.nvim.
Does not seem like it. When we run cmake --build
for example, it ends with various outputs with different build systems and each command has a different final output.
The only time, we can detect if the process has exited (sucessfully or not), would be when the terminal is ready for the next user input.
Something like when we do ls
on a large directory, we can detect that it finished only when we see the input prompt: ~$
from cmake-tools.nvim.
@Civitasv, check out this hacky solution from reddit.
We can schedule wrap a function to get lines immediately capture lines on function completion and only trigger it when we get the output is re-read from the terminal buffer, which would happen on function completion.
from cmake-tools.nvim.
Is this a good strategy?
from cmake-tools.nvim.
That’s really really good!
from cmake-tools.nvim.
@Civitasv, can you check this out(currently, where I am stuck 😅)
The output from the child process in the buffer keeps getting overwritten.
from cmake-tools.nvim.
I will test it tomorrow. Currently I have no clue.
from cmake-tools.nvim.
So sorry..., the issue was specific to powershell, an not neovim. Found a way to wrap the commands inside a process and get the child pids for it:
There should be a similar way to do this for unix.
from cmake-tools.nvim.
Related Issues (20)
- Symlink compile_commands.json doesn't appear to work on windows HOT 7
- quick start HOT 4
- on_exit in default terminal.lua HOT 1
- How to choose target when every build/run HOT 18
- targets in buildPresets of CMakePresets.json not honored
- Build Directory Creation will Fail if it Requires a Recursive Create on Windows HOT 4
- Build Directory and Build Targets Not Always Found HOT 11
- Support fish shell HOT 4
- CMakeRun No Output on Windows HOT 5
- Slowdown every time cursor is moved HOT 4
- Whitespace makes CMakeGenerate error HOT 3
- Directory path starting with number causes error
- Feature:Easier to use
- neotest-ctest adapter HOT 3
- Presets are not ignored when `cmake_use_preset` is set to false HOT 5
- Copy compile_commands to a user specified directory after CMake Configure
- target filename HOT 2
- Suggestion: provide a stable callback-based lua API HOT 2
- Automatic split size?
- Disable *cmake-tools* scratch buffer
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cmake-tools.nvim.