Code Monkey home page Code Monkey logo

Comments (43)

Civitasv avatar Civitasv commented on July 30, 2024 1

I think it works as expected. Time to make a PR. 😸

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024 1

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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024 1

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)
image

Including multiple processes...

image

After both child processes exit:

image

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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

This is a translation from this video: https://youtu.be/NBGD7GoqafY
Plus some window handling

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

Interesting! I will try to test it today.

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

Can you describe more about how should I test it? Especially where should I place this chunk of codes in?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

@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:

  1. Each executable can have a separate terminal instance to run it, with their own args as mentioned by @hfn92 in #53.
  2. 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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

@Civitasv, you can set false or true for various settings and check it.

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

Maybe you can make a PR, so I can get a test environment?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

Multi window, Multi tab handling for a single buffer

You can directly source this file without any dependencies.

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

And so that we can keep the discussion in that PR.

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

Civitasv avatar Civitasv commented on July 30, 2024

Okay.

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

Civitasv avatar Civitasv commented on July 30, 2024

To see the effect, I can just source this file?

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

test
Don't know if it works fine?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

The change the options on the top of the file. you can disable the print statements

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

To see the effect, I can just source this file?

Yes

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

@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.

Civitasv avatar Civitasv commented on July 30, 2024

Is there something like mutex lock in lua?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

Civitasv avatar Civitasv commented on July 30, 2024

Is it suitable to use coroutine? I think coroutine should use with async operation.

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

I wonder if it is okay to just let it busy wait?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

How do we do that?

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

Can we use this async feature of plenary?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

Civitasv avatar Civitasv commented on July 30, 2024

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.

Civitasv avatar Civitasv commented on July 30, 2024

With a provider call, there will be a consumer call. Do you think it is okay?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

Civitasv avatar Civitasv commented on July 30, 2024

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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

Tried this. Since the terminal job is indefinite(as we resue the terminal for serial commands), the job keeps running.

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

Civitasv avatar Civitasv commented on July 30, 2024

Can we get notified when command finished?

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

@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.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

Is this a good strategy?

from cmake-tools.nvim.

Civitasv avatar Civitasv commented on July 30, 2024

That’s really really good!

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

@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.

Civitasv avatar Civitasv commented on July 30, 2024

I will test it tomorrow. Currently I have no clue.

from cmake-tools.nvim.

rohit-kumar-j avatar rohit-kumar-j commented on July 30, 2024

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:

neovim/neovim#23777 (comment)

There should be a similar way to do this for unix.

from cmake-tools.nvim.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.