Skip to content

Commit 53bc526

Browse files
authored
feat(terminal): add option to use an alias for terminal runner (#257)
* feat(terminal): add option to use an alias for terminal runner Using an alias allows hiding the lengthy details needed to run the target. It also hides the used workaround to catch the commands return code and keeps the command line clean * feat(terminal): update cmake alias target introduces new env vars to hold the build dir and launch target used by the shell alias. Also comes with a function to update the env vars when a launch command or build directory changed * refactor(terminal): remove unused function --------- Co-authored-by: Denzel <>
1 parent 15a095c commit 53bc526

2 files changed

Lines changed: 129 additions & 83 deletions

File tree

lua/cmake-tools/const.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ local const = {
119119
start_insert = false, -- If you want to enter terminal with :startinsert upon using :CMakeRun
120120
focus = false, -- Focus on terminal when cmake task is launched.
121121
do_not_add_newline = false, -- Do not hit enter on the command inserted when using :CMakeRun, allowing a chance to review or modify the command before hitting enter.
122+
use_shell_alias = false, -- Hide the implementation details used to run the built target by using a shell alias
122123
},
123124
},
124125
},

lua/cmake-tools/terminal.lua

Lines changed: 128 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ local _terminal = {
77
id = nil, -- id for the unified terminal
88
id_old = nil, -- Old id to keep track of the buffer
99
}
10-
-- this coroutine will be used to check when command exits and runs on_exit function
11-
local on_exit_coroutine
10+
11+
local last_run_config = {
12+
build_dir = nil,
13+
launch_cmd = nil,
14+
}
1215

1316
function _terminal.has_active_job(opts)
1417
if _terminal.id then
@@ -479,40 +482,6 @@ function _terminal.get_buffers_with_prefix(prefix)
479482
return filtered_buffers
480483
end
481484

482-
function _terminal.prepare_cmd_for_run(cmd, env, args, cwd)
483-
local full_cmd = ""
484-
485-
-- Launch form executable's build directory by default
486-
full_cmd = "cd " .. utils.transform_path(cwd) .. " &&"
487-
488-
if osys.iswin32 then
489-
for k, v in pairs(env) do
490-
full_cmd = full_cmd .. " set " .. k .. "=" .. v .. "&&"
491-
end
492-
else
493-
for k, v in pairs(env) do
494-
full_cmd = full_cmd .. " " .. k .. "=" .. v .. ""
495-
end
496-
end
497-
498-
full_cmd = full_cmd .. " " .. utils.transform_path(cmd)
499-
500-
if osys.islinux or osys.iswsl or osys.ismac then
501-
full_cmd = " " .. full_cmd -- adding a space in front of the command prevents bash from recording the command in the history (if configured)
502-
end
503-
504-
-- Add args to the cmd
505-
for _, arg in ipairs(args) do
506-
full_cmd = full_cmd .. " " .. arg
507-
end
508-
509-
if osys.iswin32 then -- wrap in sub process to prevent env vars from being persited
510-
full_cmd = 'cmd /C "' .. full_cmd .. '"'
511-
end
512-
513-
return full_cmd
514-
end
515-
516485
local get_tmp_dir = function()
517486
return vim.fn.stdpath("data") .. "/cmake-tools-tmp"
518487
end
@@ -600,53 +569,143 @@ end
600569
---@param on_exit function|nil function to be called on exit the terminal will pass commands exit code as an argument
601570
---@param on_output any !unused here added for the sake of unification
602571
function _terminal.run(cmd, env_script, env, args, cwd, opts, on_exit, on_output)
603-
local full_cmd = _terminal.prepare_cmd_for_run(cmd, env, args, cwd)
572+
local function prepare_run(cmd, env, args, cwd)
573+
-- Escape all special pattern characters
574+
local escapedCwd = cwd:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
575+
cmd = cmd:gsub(escapedCwd, osys.iswin32 and ".\\" or "./")
576+
cwd = utils.transform_path(cwd)
577+
local envTbl = {}
578+
local fmtStr = osys.iswin32 and "set %s=%s" or "%s=%s"
579+
for k, v in pairs(env) do
580+
table.insert(envTbl, string.format(fmtStr, k, v))
581+
end
582+
env = table.concat(envTbl, " ")
583+
args = table.concat(args, " ")
584+
585+
return cmd, env, args, cwd
586+
end
604587

605-
local prefix = opts.prefix_name -- [CMakeTools]
606-
create_lock_file()
607588
-- prefix is added to the terminal name because the reposition_term() function needs to find it
608589
local terminal_already_exists, buffer_idx = _terminal.create_if_not_exists(
609-
prefix .. opts.name, -- [CMakeTools]Executor Terminal/Runner Terminal
590+
opts.prefix_name .. opts.name, -- [CMakeTools]Executor Terminal/Runner Terminal
610591
opts
611592
)
612593
_terminal.id = buffer_idx
613594

614595
-- Reposition the terminal buffer, before sending commands
615596
local final_win_id = _terminal.reposition(opts)
616597

617-
--- NOTE: env_script needs to be run only once if the terminal buffer does not already exist
618-
--- We compare the old and the new id and only if they are not the same, plus if the terminal exists,
619-
-- only then, we do not reinitialize the environment, else we reinit the env
620-
if not terminal_already_exists or _terminal.id_old ~= _terminal.id then
621-
_terminal.id_old = _terminal.id
622-
_terminal.send_data_to_terminal(buffer_idx, env_script, {
623-
win_id = final_win_id,
624-
prefix = opts.prefix_name,
625-
split_direction = opts.split_direction,
626-
split_size = opts.split_size,
627-
start_insert = opts.start_insert,
628-
focus = opts.focus,
629-
})
598+
local exit_handler = (osys.iswin32 and "& " or "; ") .. get_command_handling_on_exit()
599+
local final_cmd, final_env, final_args, build_dir = prepare_run(cmd, env, args, cwd)
600+
local full_cmd
601+
local call_update
602+
603+
local termOpts = {
604+
win_id = final_win_id,
605+
prefix = opts.prefix_name,
606+
split_direction = opts.split_direction,
607+
split_size = opts.split_size,
608+
start_insert = opts.start_insert,
609+
auto_resize = opts.auto_resize,
610+
focus = opts.focus,
611+
}
612+
613+
if not opts.use_shell_alias then
614+
full_cmd = (osys.iswin32 and 'cmd /C "' or "")
615+
.. "cd "
616+
.. build_dir
617+
.. " && "
618+
.. (final_env .. (final_env ~= "" and " " or ""))
619+
.. final_cmd
620+
.. ((final_args ~= "" and " " or "") .. final_args)
621+
.. (osys.iswin32 and '"' or "")
622+
.. exit_handler
623+
else
624+
if osys.iswin32 then
625+
error("using a shell alias is currently not suported for windows")
626+
end
627+
628+
local alias_name = "cmake_run_target"
629+
local update_function = "cmake_update_target"
630+
631+
full_cmd = (final_env ~= "" and (final_env .. " ") or "") .. alias_name .. " " .. final_args
632+
if not is_fish_shell and osys.islinux or osys.iswsl or osys.ismac then
633+
-- adding a space in front of the command prevents bash from recording the command in the history (if configured)
634+
full_cmd = " " .. full_cmd
635+
end
636+
637+
call_update = string.format(" %s '%s' '%s'", update_function, build_dir, final_cmd)
638+
639+
--- NOTE: env_script needs to be run only once if the terminal buffer does not already exist
640+
--- We compare the old and the new id and only if they are not the same, plus if the terminal exists,
641+
-- only then, we do not reinitialize the environment, else we reinit the env
642+
if not terminal_already_exists or _terminal.id_old ~= _terminal.id then
643+
local env_var_build = "CMAKE_TOOLS_BUILD_DIR"
644+
local env_var_target = "CMAKE_TOOLS_LAUNCH_TARGET"
645+
646+
local userEnvScript = env_script
647+
local fmt = {}
648+
649+
if is_fish_shell() then
650+
fmt.update_func = "function %s; set -g %s $argv[1]; set -g %s $argv[2]; end; "
651+
fmt.run_func = "function %s; cd $%s && eval '$%s $argv' %s; end; "
652+
else
653+
fmt.update_func = "%s() { export %s=$1; export %s=$2; }; "
654+
fmt.run_func = "%s() { cd $%s && eval '$%s $*' %s; }; "
655+
end
656+
657+
-- Depending how the user defined env_script ends, we have to strip a trailing
658+
-- semicolon and replace it by a && to only clear the console if the user defined
659+
-- env_script ran successfully
660+
if userEnvScript and userEnvScript:match("^%s*$") == nil then
661+
if userEnvScript:match(";%s*$") then
662+
userEnvScript = userEnvScript:match("^(.-);%s*$") .. "&&"
663+
elseif not userEnvScript:match("&&%s*$") then
664+
userEnvScript = userEnvScript .. "&&"
665+
end
666+
end
667+
668+
env_script = string.format(fmt.update_func, update_function, env_var_build, env_var_target)
669+
.. call_update
670+
.. " && "
671+
.. string.format(fmt.run_func, alias_name, env_var_build, env_var_target, exit_handler)
672+
.. userEnvScript
673+
.. "clear"
674+
675+
if not is_fish_shell and osys.islinux or osys.iswsl or osys.ismac then
676+
-- adding a space in front of the command prevents bash from recording the command in the history (if configured)
677+
env_script = " " .. env_script
678+
end
679+
680+
_terminal.id_old = _terminal.id
681+
_terminal.send_data_to_terminal(buffer_idx, env_script, termOpts)
682+
end
683+
end
684+
685+
if
686+
opts.use_shell_alias
687+
and (last_run_config.build_dir ~= build_dir or last_run_config.launch_cmd ~= final_cmd)
688+
then
689+
if last_run_config.build_dir then
690+
_terminal.send_data_to_terminal(buffer_idx, call_update, termOpts)
691+
end
692+
693+
last_run_config.build_dir = build_dir
694+
last_run_config.launch_cmd = final_cmd
630695
end
631696

697+
termOpts.do_not_add_newline = opts.do_not_add_newline
698+
699+
create_lock_file()
700+
632701
-- Send final cmd to terminal
633-
local chain_symb = osys.iswin32 and " & " or " ; "
634-
_terminal.send_data_to_terminal(
635-
buffer_idx,
636-
full_cmd .. chain_symb .. get_command_handling_on_exit(),
637-
{
638-
win_id = final_win_id,
639-
prefix = opts.prefix_name,
640-
split_direction = opts.split_direction,
641-
split_size = opts.split_size,
642-
start_insert = opts.start_insert,
643-
focus = opts.focus,
644-
auto_resize = opts.auto_resize,
645-
do_not_add_newline = opts.do_not_add_newline,
646-
}
647-
)
702+
_terminal.send_data_to_terminal(buffer_idx, full_cmd, termOpts)
703+
704+
-- this coroutine will be used to check when command exits and runs on_exit function
705+
local on_exit_coroutine
648706
on_exit_coroutine = coroutine.create(function()
649-
while utils.file_exists(get_lock_file_path()) do
707+
local lock_fie_path = get_lock_file_path()
708+
while utils.file_exists(lock_fie_path) do
650709
vim.defer_fn(function()
651710
coroutine.resume(on_exit_coroutine)
652711
end, 25)
@@ -658,20 +717,6 @@ function _terminal.run(cmd, env_script, env, args, cwd, opts, on_exit, on_output
658717
coroutine.resume(on_exit_coroutine)
659718
end
660719

661-
function _terminal.prepare_launch_path(path)
662-
if osys.iswin32 then
663-
path = '"' .. path .. '"' -- The path is kept in double quotes ... Windows Duh!
664-
elseif osys.islinux then
665-
path = path
666-
elseif osys.iswsl then
667-
path = path
668-
elseif osys.ismac then
669-
path = path
670-
end
671-
672-
return path
673-
end
674-
675720
function _terminal.close(opts)
676721
if not _terminal.id then
677722
log.info("There is no terminal instance")

0 commit comments

Comments
 (0)