forked from Civitasv/cmake-tools.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathterminal.lua
More file actions
808 lines (707 loc) · 27.2 KB
/
terminal.lua
File metadata and controls
808 lines (707 loc) · 27.2 KB
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
local osys = require("cmake-tools.osys")
local log = require("cmake-tools.log")
local utils = require("cmake-tools.utils")
---@class terminal : executor, runner
local _terminal = {
id = nil, -- id for the unified terminal
id_old = nil, -- Old id to keep track of the buffer
}
local last_run_config = {
build_dir = nil,
launch_cmd = nil,
}
function _terminal.has_active_job(opts)
if _terminal.id then
-- first, check if this buffer is valid
if not vim.api.nvim_buf_is_valid(_terminal.id) then
return false
end
local main_pid = vim.api.nvim_buf_get_var(_terminal.id, "terminal_job_pid")
local child_procs = vim.api.nvim_get_proc_children(main_pid)
if next(child_procs) then
return true
else
return false
end
end
return false
end
function _terminal.show(opts)
if not _terminal.id then
log.info("There is no terminal instance")
return
end
local win_id = _terminal.reposition(opts)
if win_id ~= -1 then
-- The window is alive, so we set buffer in window
vim.api.nvim_win_set_buf(win_id, _terminal.id)
if opts.auto_resize then
if opts.split_direction == "horizontal" then
vim.api.nvim_win_set_height(win_id, opts.split_size)
else
vim.api.nvim_win_set_width(win_id, opts.split_size)
end
end
elseif win_id >= -1 then
-- The window is not active, we need to create a new buffer
vim.cmd(":" .. opts.split_direction .. " " .. opts.split_size .. "sp") -- Split
vim.api.nvim_win_set_buf(0, _terminal.id)
else
-- log.error("Invalid window Id!")
-- do nothing
end
end
-- Make a new terminal named term_name
function _terminal.new_instance(term_name, opts)
local buffers_before = vim.api.nvim_list_bufs()
-- Now create the plit
vim.cmd(":" .. opts.split_direction .. " " .. opts.split_size .. "sp | :term") -- Creater terminal in a split
-- local new_name = vim.fn.fnamemodify(term_name, ":t") -- Extract only the terminal name and reassign it
vim.api.nvim_buf_set_name(vim.api.nvim_get_current_buf(), term_name) -- Set the buffer name
if opts.split_direction == "horizontal" then
vim.wo.winfixheight = true
else
vim.wo.winfixwidth = true
end
vim.cmd(":setlocal laststatus=3") -- Let there be a single status/lualine in the neovim instance
-- Renamming a terminal buffer creates a new hidden buffer, so duplicate terminals need to be deleted
local new_buffers_list = vim.api.nvim_list_bufs()
local diff_buffers_list = _terminal.symmetric_difference(buffers_before, new_buffers_list)
_terminal.delete_duplicate_terminal_buffers_except(term_name, diff_buffers_list)
-- This is mainly for users to do filtering if necessary, as termial does not have a default type.
-- Example: using a filter in 'hardtime.nvim' to make sure
-- we can use chained hjkl keys in sucession in the terminal to scroll.
-- It also makes it easier to get the terminals that are unique to cmake_tools
vim.api.nvim_buf_set_option(vim.api.nvim_get_current_buf(), "filetype", "cmake_tools_terminal")
_terminal.delete_scratch_buffers()
local new_buffer_idx = _terminal.get_buffer_number_from_name(term_name)
return new_buffer_idx
end
function _terminal.symmetric_difference(list1, list2)
local unique_numbers = {}
local list1_set = {}
local list2_set = {}
-- Create a set from list1
for _, number in ipairs(list1) do
list1_set[number] = true
end
-- Create a set from list2 and add numbers to unique_numbers if not in list1
for _, number in ipairs(list2) do
if not list1_set[number] then
table.insert(unique_numbers, number)
end
list2_set[number] = true
end
-- Add numbers from list1 that are not in list2 to unique_numbers
for _, number in ipairs(list1) do
if not list2_set[number] then
table.insert(unique_numbers, number)
end
end
return unique_numbers
end
function _terminal.delete_duplicate_terminal_buffers_except(buffer_name, buffer_list)
for _, buffer in ipairs(buffer_list) do
local name = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buffer), ":t")
-- if string.match(name, buffer_name) == name then
if name == buffer_name then
-- print('DONOT Delete: name in delete duplicate: ' .. name .. ', bufnr: ' .. buffer)
else
vim.cmd(":bw! " .. buffer)
end
end
end
function _terminal.delete_scratch_buffers()
local buffers = vim.api.nvim_list_bufs()
for _, buffer in ipairs(buffers) do
local name = vim.api.nvim_buf_get_name(buffer)
if string.match(name, "^scratch_") then
vim.api.nvim_buf_delete(buffer, { force = true })
end
end
end
function _terminal.get_buffer_number_from_name(buffer_name)
local buffers = vim.api.nvim_list_bufs()
for _, bufnr in ipairs(buffers) do
local name = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ":t")
-- print('get_buffer_number_from_name: ' .. name .. ", required name: " .. buffer_name)
if name == buffer_name then
-- print(' HIT! get_buffer_number_from_name: ' .. name .. ", required name: " .. buffer_name)
return bufnr
end
end
return nil -- Buffer with the given name not found
end
function _terminal.send_data_to_terminal(buffer_idx, cmd, opts)
if not opts or not opts.do_not_add_newline then
if osys.iswin32 then
cmd = cmd .. " \r"
elseif osys.ismac then
cmd = cmd .. " \n"
elseif osys.islinux then
cmd = cmd .. " \n"
elseif osys.iswsl then
--NOTE: Techinically, wsl-2 and linux are detected as linux. We might see a diferrence in wsl-1 vs wsl-2
cmd = cmd .. " \n"
end
else
-- Append a space but NOT the newline, so that the user has a chance to review the command before executing it
cmd = cmd .. " "
end
if opts and opts.win_id ~= -1 then
-- The window is alive, so we set buffer in window
vim.api.nvim_win_set_buf(opts.win_id, buffer_idx)
if opts.auto_resize then
if opts.split_direction == "horizontal" then
vim.api.nvim_win_set_height(opts.win_id, opts.split_size)
else
vim.api.nvim_win_set_width(opts.win_id, opts.split_size)
end
end
elseif opts and opts.win_id >= -1 then
-- The window is not active, we need to create a new buffer
vim.cmd(":" .. opts.split_direction .. " " .. opts.split_size .. "sp") -- Split
vim.api.nvim_win_set_buf(0, buffer_idx) -- Set buffer to newly created window
else
-- log.error("Invalid window Id!")
-- do nothing
end
-- Now, the cmake buffer's window is currently in focus
if opts and opts.focus then
-- We want to focus on the newly set terminal
vim.api.nvim_set_current_win(opts.win_id)
if opts.start_insert then
vim.cmd("startinsert")
end
else
-- We want to focus on our currently focused window and not ther cmake terminal
local name = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(0))
local basename = vim.fn.fnamemodify(name, ":t")
if opts and (basename:sub(1, #opts.prefix) == opts.prefix) then -- If currently focused buffer is cmake buffer then ...
-- Now we check again if the buffer needs to be focused as the user might be scrolling
-- a cmake buffer and execute a :CMakeCommand, so we do not want to move their
-- cursor out of the cmake buffer, as it can be annoying
if opts and not opts.focus then
vim.cmd("wincmd p") -- Goes back to previous window: Equivalent to [[ CTRL-W p ]]
end
end
end
-- Focus on the last line in the buffer to keep the scrolling output
-- [[ We keep this option enabled by default because when users scroll the buffer and run :CMakeCommands,
-- we must scroll the buffer even if they are focused on it
-- ]]
vim.api.nvim_buf_call(buffer_idx, function()
local type = vim.api.nvim_get_option_value("buftype", {
buf = buffer_idx,
})
if type == "terminal" then
vim.cmd("normal! G")
end
end)
-- Finally send data to the terminal for execution
local chan = vim.api.nvim_buf_get_var(buffer_idx, "terminal_job_id")
vim.api.nvim_chan_send(chan, cmd)
end
function _terminal.create_if_not_exists(term_name, opts)
local term_idx = nil
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
local name = vim.api.nvim_buf_get_name(bufnr)
name = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ":t")
if name == term_name then
term_idx = bufnr
else
end
end
local terminal_already_exists = false
if term_idx ~= nil and vim.api.nvim_buf_is_valid(term_idx) then
local type = vim.api.nvim_get_option_value("buftype", {
buf = term_idx,
})
if type == "terminal" then
terminal_already_exists = true
else
vim.api.nvim_buf_delete(term_idx, { force = true })
end
end
if not terminal_already_exists then
term_idx = _terminal.new_instance(term_name, opts)
-- does_terminal_already_exist terminal will be default (false)
end
return terminal_already_exists, term_idx
end
function _terminal.reposition(opts)
local all_open_cmake_terminal_buffers = _terminal.get_buffers_with_prefix(opts.prefix_name)
-- Check all cmake terminals with buffers
-- Check how, where and weather the buffers are displayed in the neovim instance
local all_buffer_display_info = {}
for _, buffer in ipairs(all_open_cmake_terminal_buffers) do
table.insert(
all_buffer_display_info,
_terminal.get_buffer_display_info(buffer, {
ignore_current_tab = false, -- Set this to true to get info of all tabs execept current tab
get_unindexed_list = false, -- Set this to true for viewing a visually appealing nice table
})
)
end
local single_terminal_per_instance = opts.single_terminal_per_instance
local single_terminal_per_tab = opts.single_terminal_per_tab
local static_window_location = opts.keep_terminal_static_location
local final_win_id = -1 -- If -1, then a new window needs to be created, otherwise, we must return an existing winid
if single_terminal_per_instance then
if static_window_location then
_terminal.close_window_from_tabs_with_prefix(true, opts) -- Close all cmake windows in all other tabs
local buflist = _terminal.check_if_cmake_buffers_are_displayed_across_all_tabs(opts) -- Get list of all buffers that are displayed in current tab
if next(buflist) then
for i = 1, #buflist do -- Buffers exist in current tab, so close all except first buffer in buflist
if i > 1 then
vim.api.nvim_win_close(buflist[i], false)
end
end
final_win_id = buflist[1]
end
else
_terminal.close_window_from_tabs_with_prefix(true, opts) -- Close all cmake windows in all tabs
local buflist = _terminal.check_if_cmake_buffers_are_displayed_across_all_tabs(opts) -- Get list of all buffers that are displayed in current tab
if next(buflist) then
-- Buffers exist in current tab, so close all buffers in buflist
for i = 1, #buflist do
if i > 1 then
vim.api.nvim_win_close(buflist[i], false)
end
end
end
final_win_id = -1
end
elseif single_terminal_per_tab then
if static_window_location then
local buflist = _terminal.check_cmake_buffers_are_displayed_in_current_tab(opts)
if next(buflist) then
for i = 1, #buflist do -- Buffers exist in current tab, so close all except first buffer in buflist
if i > 1 then
vim.api.nvim_win_close(buflist[i], false)
end
end
final_win_id = buflist[1]
else
final_win_id = -1
end
else
local buflist = _terminal.check_cmake_buffers_are_displayed_in_current_tab(opts)
if next(buflist) then
for i = 1, #buflist do -- Buffers exist in current tab, so close all except first buffer in buflist
vim.api.nvim_win_close(buflist[i], false)
end
end
end
else
-- Launch multiple terminals
final_win_id = 0
log.warn("Caution: Multiple termianls may clutter your workspace!")
end
return final_win_id
end
-- Matches all the tabs and wins with buffer_idx and retunrs list of winid's indexed with resprect to their tabpages
-- Set opts.get_unindexed_list = true for getting an iterable list of values that you can use to close windows with. These are returned as a for the current buffer.
-- Only set opts.get_unindexed_list to false for viewing buffer info... i.e. how they are laid out across tabs and windows
-- Set opts.ignore_current_tab to true if you want a list of windows only from other tabs.
function _terminal.get_buffer_display_info(buffer_idx, opts)
local buffer_display_info = {}
if opts and opts.get_unindexed_list then
buffer_display_info = {}
else
buffer_display_info = { buffer_idx = buffer_idx, windows = {} }
end
for _, tabpage in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
if vim.api.nvim_win_get_buf(win) == buffer_idx then
--
-- This may seem a little hairy, but it has only 2 options
--
if opts and opts.get_unindexed_list then
if opts.ignore_current_tab then
if vim.api.nvim_get_current_tabpage() == tabpage then
-- Ignore current tabpage if set: do nothing
else
table.insert(buffer_display_info, win)
end
else
table.insert(buffer_display_info, win)
end
else
if opts.ignore_current_tab then
local tabpage_id = vim.api.nvim_tabpage_get_number(tabpage)
if opts.ignore_current_tab and vim.api.nvim_get_current_tabpage() == tabpage then
-- Ignore current tabpage if set: do nothing
else
table.insert(buffer_display_info.windows, { tabpage_id = tabpage_id, win = win })
end
else
local tabpage_id = vim.api.nvim_tabpage_get_number(tabpage)
if opts.ignore_current_tab and vim.api.nvim_get_current_tabpage() == tabpage then
-- Ignore current tabpage if set: do nothing
else
table.insert(buffer_display_info.windows, { tabpage_id = tabpage_id, win = win })
end
end
end
--
--
--
end
end
end
return buffer_display_info
end
-- Close all window in all tabs by prefix.
-- @param ignore_current_tab: if ignore current tab
-- @param opts: { prefix_name }
function _terminal.close_window_from_tabs_with_prefix(ignore_current_tab, opts)
local buffers = _terminal.get_buffers_with_prefix(opts.prefix_name)
local unindexed_window_list = {}
for _, buffer in ipairs(buffers) do
local windows_open_for_buffer = _terminal.get_buffer_display_info(buffer, {
ignore_current_tab = ignore_current_tab,
get_unindexed_list = true,
})
for _, win in ipairs(windows_open_for_buffer) do
table.insert(unindexed_window_list, win)
end
end
for i = 1, #unindexed_window_list do
if i > 1 then
vim.api.nvim_win_close(unindexed_window_list[i], false)
end
end
end
function _terminal.check_cmake_buffers_are_displayed_in_current_tab(opts)
local all_open_cmake_terminal_buffers = _terminal.get_buffers_with_prefix(opts.prefix_name)
local current_tabpage = vim.api.nvim_get_current_tabpage()
local current_windows = vim.api.nvim_tabpage_list_wins(current_tabpage)
local displayed_windows = {}
for _, win in ipairs(current_windows) do
local win_bufnr = vim.api.nvim_win_get_buf(win)
if vim.tbl_contains(all_open_cmake_terminal_buffers, win_bufnr) then
table.insert(displayed_windows, win)
end
end
return displayed_windows
end
function _terminal.check_if_cmake_buffers_are_displayed_across_all_tabs(opts)
local all_open_cmake_terminal_buffers = _terminal.get_buffers_with_prefix(opts.prefix_name)
local unindexed_window_list = {}
for _, buffer in ipairs(all_open_cmake_terminal_buffers) do
local windows_open_for_buffer = _terminal.get_buffer_display_info(buffer, {
ignore_current_tab = false,
get_unindexed_list = true,
})
for _, win in ipairs(windows_open_for_buffer) do
table.insert(unindexed_window_list, win)
end
end
-- Now, we return the list of buffers
return unindexed_window_list
end
function _terminal.get_windows_in_other_tabs(buflist, opts)
local current_tabpage = vim.api.nvim_get_current_tabpage()
local windows = {}
for _, tabpage in ipairs(vim.api.nvim_list_tabpages()) do
if not opts.ignore_current_tab or tabpage ~= current_tabpage then
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
local bufnr = vim.api.nvim_win_get_buf(win)
if vim.tbl_contains(buflist, bufnr) then
table.insert(windows, win)
end
end
end
end
return windows
end
function _terminal.get_buffers_with_prefix(prefix)
local buffers = vim.api.nvim_list_bufs()
local filtered_buffers = {}
for _, buffer in ipairs(buffers) do
local name = vim.api.nvim_buf_get_name(buffer)
local basename = vim.fn.fnamemodify(name, ":t")
if basename:sub(1, #prefix) == prefix then
table.insert(filtered_buffers, buffer)
end
end
return filtered_buffers
end
local get_tmp_dir = function()
return vim.fn.stdpath("data") .. "/cmake-tools-tmp"
end
---@param file_name string name of the file
---@return string
local get_tmp_file_path = function(file_name)
return get_tmp_dir() .. "/" .. file_name
end
---@param file_name string
local create_tmp_file = function(file_name)
utils.mkdir(get_tmp_dir())
local tmp_file = io.open(get_tmp_file_path(file_name), "w")
if tmp_file then
tmp_file:close()
end
end
---@return string
local get_lock_file_path = function()
return get_tmp_file_path(".lock")
end
---@return string
local get_last_exit_code_file_path = function()
return get_tmp_file_path("exit_code")
end
local create_lock_file = function()
create_tmp_file(".lock")
end
local is_fish_shell = function()
local shell = vim.o.shell
return string.find(shell, "fish")
end
local is_cmd = function()
return string.find(vim.o.shell, "cmd") ~= nil
end
local is_power_shell = function()
return string.find(vim.o.shell, "powershell") ~= nil
end
local is_pwsh = function()
return string.find(vim.o.shell, "pwsh") ~= nil
end
local is_windows_shell = function()
return (is_pwsh() or is_cmd() or is_power_shell())
end
---creates command that handles all of our post command stuff for on_exit handling
---@return string
local get_command_handling_on_exit = function()
local exit_code_file_path = get_last_exit_code_file_path()
local lock_file_path = get_lock_file_path()
if osys.iswin32 then
if is_power_shell() or is_pwsh() then
exit_code_file_path = exit_code_file_path:gsub("/", "\\")
lock_file_path = lock_file_path:gsub("/", "\\")
return "`$ec = `$LASTEXITCODE; Set-Content -Path "
.. exit_code_file_path
.. " -Value `$ec; Remove-Item "
.. lock_file_path
elseif is_cmd() then
exit_code_file_path = exit_code_file_path:gsub("/", "\\")
lock_file_path = lock_file_path:gsub("/", "\\")
return "echo !errorlevel! > " .. exit_code_file_path .. " && del /Q " .. lock_file_path
else
-- bash-like on windows
exit_code_file_path = exit_code_file_path:gsub("\\", "/")
lock_file_path = lock_file_path:gsub("\\", "/")
end
end
local exit_op = "$?"
local escape_rm = " \\rm -f "
local and_op = " &&"
if is_fish_shell() then
exit_op = "$status"
escape_rm = " command rm -f "
end
return "echo " .. exit_op .. " > " .. exit_code_file_path .. and_op .. escape_rm .. lock_file_path
end
---tries to read the number stored in get_last_exit_code_file_path() file
---@return number|nil
local get_last_exit_code = function()
local file = io.open(get_last_exit_code_file_path(), "r")
if not file then
print("Could not find last tmp file conaining exit code")
return nil
end
local code = tonumber(file:read("*n"))
file:close()
return code
end
---
---@param cmd string command that will be run in the terminal
---@param env_script any
---@param env any
---@param args string[]
---@param cwd string
---@param opts any
---@param on_exit function|nil function to be called on exit the terminal will pass commands exit code as an argument
---@param on_output any !unused here added for the sake of unification
function _terminal.run(cmd, env_script, env, args, cwd, opts, on_exit, on_output)
local function prepare_run(cmd, env, args, cwd)
-- Escape all special pattern characters
local escapedCwd = cwd:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
cmd = cmd:gsub(escapedCwd, (osys.iswin32 and is_windows_shell()) and ".\\" or "./")
if osys.iswin32 and not is_windows_shell() then
cwd = cwd:gsub("\\", "/")
end
cwd = utils.shell_quote(cwd)
local envTbl = {}
local fmtStr = osys.iswin32 and "set %s=%s" or "%s=%s"
for k, v in pairs(env) do
table.insert(envTbl, string.format(fmtStr, k, v))
end
env = table.concat(envTbl, " ")
args = table.concat(vim.tbl_map(utils.shell_quote, args), " ")
return cmd, env, args, cwd
end
-- prefix is added to the terminal name because the reposition_term() function needs to find it
local terminal_already_exists, buffer_idx = _terminal.create_if_not_exists(
opts.prefix_name .. opts.name, -- [CMakeTools]Executor Terminal/Runner Terminal
opts
)
_terminal.id = buffer_idx
-- Reposition the terminal buffer, before sending commands
local final_win_id = _terminal.reposition(opts)
local final_cmd, final_env, final_args, build_dir = prepare_run(cmd, env, args, cwd)
local full_cmd
local call_update
local termOpts = {
win_id = final_win_id,
prefix = opts.prefix_name,
split_direction = opts.split_direction,
split_size = opts.split_size,
start_insert = opts.start_insert,
auto_resize = opts.auto_resize,
focus = opts.focus,
}
if not opts.use_shell_alias then
local shell_prefix, cd_sep, shell_suffix, chain_symb = "", " && ", "", "; "
if osys.iswin32 and is_windows_shell() then
if is_pwsh() or is_power_shell() then
shell_prefix = vim.o.shell .. ' -Command "'
cd_sep = is_pwsh() and " && " or "; " -- 5.1 vs pwsh 7+
else
shell_prefix = 'cmd /V:ON /C "'
cd_sep = " && "
chain_symb = " & "
end
shell_suffix = '"'
end
local exit_handler = chain_symb .. get_command_handling_on_exit()
full_cmd = shell_prefix
.. "cd "
.. build_dir
.. cd_sep
.. (final_env .. (final_env ~= "" and " " or ""))
.. final_cmd
.. ((final_args ~= "" and " " or "") .. final_args)
.. exit_handler
.. shell_suffix
else
if osys.iswin32 and is_windows_shell() then
error("using a shell alias is currently not suported for windows")
end
local exit_handler = "; " .. get_command_handling_on_exit()
local alias_name = "cmake_run_target"
local update_function = "cmake_update_target"
full_cmd = (final_env ~= "" and (final_env .. " ") or "") .. alias_name .. " " .. final_args
if not is_fish_shell and osys.islinux or osys.iswsl or osys.ismac then
-- adding a space in front of the command prevents bash from recording the command in the history (if configured)
full_cmd = " " .. full_cmd
end
call_update = string.format(" %s '%s' '%s'", update_function, build_dir, final_cmd)
--- NOTE: env_script needs to be run only once if the terminal buffer does not already exist
--- We compare the old and the new id and only if they are not the same, plus if the terminal exists,
-- only then, we do not reinitialize the environment, else we reinit the env
if not terminal_already_exists or _terminal.id_old ~= _terminal.id then
local env_var_build = "CMAKE_TOOLS_BUILD_DIR"
local env_var_target = "CMAKE_TOOLS_LAUNCH_TARGET"
local userEnvScript = env_script
local fmt = {}
if is_fish_shell() then
fmt.update_func = "function %s; set -g %s $argv[1]; set -g %s $argv[2]; end; "
fmt.run_func = "function %s; cd $%s && eval '$%s $argv' %s; end; "
else
fmt.update_func = "%s() { export %s=$1; export %s=$2; }; "
fmt.run_func = "%s() { cd $%s && eval '$%s $*' %s; }; "
end
-- Depending how the user defined env_script ends, we have to strip a trailing
-- semicolon and replace it by a && to only clear the console if the user defined
-- env_script ran successfully
if userEnvScript and userEnvScript:match("^%s*$") == nil then
if userEnvScript:match(";%s*$") then
userEnvScript = userEnvScript:match("^(.-);%s*$") .. "&&"
elseif not userEnvScript:match("&&%s*$") then
userEnvScript = userEnvScript .. "&&"
end
end
env_script = string.format(fmt.update_func, update_function, env_var_build, env_var_target)
.. call_update
.. " && "
.. string.format(fmt.run_func, alias_name, env_var_build, env_var_target, exit_handler)
.. userEnvScript
.. "clear"
if not is_fish_shell and osys.islinux or osys.iswsl or osys.ismac then
-- adding a space in front of the command prevents bash from recording the command in the history (if configured)
env_script = " " .. env_script
end
_terminal.id_old = _terminal.id
_terminal.send_data_to_terminal(buffer_idx, env_script, termOpts)
end
end
if
opts.use_shell_alias
and (last_run_config.build_dir ~= build_dir or last_run_config.launch_cmd ~= final_cmd)
then
if last_run_config.build_dir then
_terminal.send_data_to_terminal(buffer_idx, call_update, termOpts)
end
last_run_config.build_dir = build_dir
last_run_config.launch_cmd = final_cmd
end
termOpts.do_not_add_newline = opts.do_not_add_newline
create_lock_file()
-- Send final cmd to terminal
_terminal.send_data_to_terminal(buffer_idx, full_cmd, termOpts)
-- this coroutine will be used to check when command exits and runs on_exit function
local on_exit_coroutine
on_exit_coroutine = coroutine.create(function()
local lock_fie_path = get_lock_file_path()
while utils.file_exists(lock_fie_path) do
vim.defer_fn(function()
coroutine.resume(on_exit_coroutine)
end, 25)
coroutine.yield()
end
_terminal.handle_exit(opts, on_exit, opts.close_on_exit)
-- if type onexit function then on_exit()
end)
coroutine.resume(on_exit_coroutine)
end
function _terminal.close(opts)
if not _terminal.id then
log.info("There is no terminal instance")
return
end
local win_id = _terminal.reposition(opts)
if win_id ~= -1 then
vim.api.nvim_win_close(win_id, false)
else
-- log.error("Invalid window Id!")
-- do nothing
end
end
function _terminal.stop(opts)
if not _terminal.has_active_job() then
return
end
local main_pid = vim.api.nvim_buf_get_var(_terminal.id, "terminal_job_pid")
local child_procs = vim.api.nvim_get_proc_children(main_pid)
for _, pid in ipairs(child_procs) do
vim.loop.kill(pid, 9)
end
end
function _terminal.is_installed()
return true
end
--- Handle when a terminal process exists
---@param opts any
---@param on_exit function|nil function to be executed on exit
---@param close_on_exit boolean close the terminal on exit
function _terminal.handle_exit(opts, on_exit, close_on_exit)
if close_on_exit then
_terminal.close(opts)
end
if type(on_exit) == "function" then
on_exit(get_last_exit_code()) -- always return success
end
end
return _terminal