diff --git a/src/basic/basic_head.h b/src/basic/basic_head.h index dcf74fe..0143a05 100644 --- a/src/basic/basic_head.h +++ b/src/basic/basic_head.h @@ -85,6 +85,14 @@ #define IF_DEBUG(x) #endif +#if OS_WINDOWS + #define IF_OS_WINDOWS_ELSE(x, y) x + #define IF_OS_WINDOWS(x) x +#else + #define IF_OS_WINDOWS_ELSE(x, y) y + #define IF_OS_WINDOWS(x) +#endif + #if OS_WINDOWS #ifndef NOMINMAX #define NOMINMAX diff --git a/src/basic/basic_os.cpp b/src/basic/basic_os.cpp index c3d79f2..672d5ed 100644 --- a/src/basic/basic_os.cpp +++ b/src/basic/basic_os.cpp @@ -257,204 +257,6 @@ API FileIter IterateFiles(Allocator alo, String path) { return it; } -struct UnixProcess { - pid_t pid; - int child_stdout_read; - int stdin_write; -}; - -API Array SplitCommand(Allocator allocator, String command_line) { - Array cmd = {allocator}; - - String curr = {}; - for (int i = 0; i < command_line.len; i += 1) { - if (command_line.data[i] == ' ') { - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - curr = {}; - } - continue; - } - if (curr.len == 0) { - curr.data = command_line.data + i; - } - curr.len += 1; - } - - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - } - - return cmd; -} - -API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { - Scratch scratch; - const int PIPE_READ = 0; - const int PIPE_WRITE = 1; - bool error = false; - - working_dir = Copy(scratch, working_dir); - chdir(working_dir.data); - - Process process = {}; - UnixProcess *plat = (UnixProcess *)&process.platform; - Array args = SplitCommand(scratch, command_line); - Array env = {scratch}; - - For (enviroment) { - Add(&env, Copy(scratch, it).data); - } - - int stdout_desc[2] = {}; - int stdin_desc[2] = {}; - posix_spawn_file_actions_t actions = {}; - - if (posix_spawn_file_actions_init(&actions) != 0) { - Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno)); - return process; - } - defer { - posix_spawn_file_actions_destroy(&actions); - }; - - if (pipe(stdout_desc) == -1) { - Error("Libc function failed: pipe, with error: %s", strerror(errno)); - return process; - } - defer { - if (error) { - close(stdout_desc[PIPE_READ]); - close(stdout_desc[PIPE_WRITE]); - } else { - close(stdout_desc[PIPE_WRITE]); - } - }; - - if (pipe(stdin_desc) == -1) { - Error("Libc function failed: pipe, with error: %s", strerror(errno)); - return process; - } - defer { - if (error) { - close(stdin_desc[PIPE_READ]); - close(stdin_desc[PIPE_WRITE]); - } else { - close(stdin_desc[PIPE_READ]); - } - }; - - error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno)); - return process; - } - - pid_t process_pid = 0; - error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0; - if (error) { - Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno)); - return process; - } - - - plat->child_stdout_read = stdout_desc[PIPE_READ]; - plat->stdin_write = stdin_desc[PIPE_WRITE]; - plat->pid = process_pid; - - if (write_stdin.len) { - WriteStdin(&process, write_stdin); - CloseStdin(&process); - } - - process.id = process_pid; - process.is_valid = true; - return process; -} - -API bool IsValid(Process *process) { - UnixProcess *plat = (UnixProcess *)&process->platform; - if (process->is_valid == false) { - return false; - } - - int status = 0; - pollfd p = {}; - p.fd = plat->child_stdout_read; - p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL; - int res = poll(&p, 1, 0); - if (res > 0) { - pid_t result = waitpid(plat->pid, &status, 0); - Assert(result != -1); - process->exit_code = WEXITSTATUS(status); - return false; - } - - return true; -} - -API void KillProcess(Process *process) { - Assert(process->is_valid); - UnixProcess *plat = (UnixProcess *)process->platform; - kill(plat->pid, SIGKILL); - process->exit_code = -1; -} - -API String PollStdout(Allocator allocator, Process *process, bool force_read) { - Assert(process->is_valid); - UnixProcess *plat = (UnixProcess *)process->platform; - - String result = {}; - result.data = AllocArray(allocator, char, 16 * 4096); - - pollfd p = {}; - p.fd = plat->child_stdout_read; - p.events = POLLIN; - int res = poll(&p, 1, 0); - if (res > 0 || force_read) { - result.len = read(plat->child_stdout_read, result.data, 4 * 4096); - } - return result; -} - -API void WriteStdin(Process *process, String string) { - if (string.len == 0) return; - Assert(process->is_valid); - - UnixProcess *plat = (UnixProcess *)process->platform; - ssize_t size = write(plat->stdin_write, string.data, string.len); - - Assert(size == string.len); -} - -API void CloseStdin(Process *process) { - UnixProcess *plat = (UnixProcess *)process->platform; - close(plat->stdin_write); -} #elif OS_WINDOWS @@ -725,210 +527,6 @@ API MakeDirResult MakeDir(String path) { return result; } -struct Win32Process { - HANDLE handle; - HANDLE child_stdout_read; - HANDLE child_stdout_write; - HANDLE child_stdin_read; - HANDLE child_stdin_write; -}; -// static_assert(sizeof(Win32Process) < sizeof(Process::platform)); - -static void Win32ReportError(String msg, String cmd) { - LPVOID lpMsgBuf; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); - defer { LocalFree(lpMsgBuf); }; - char *buff = (char *)lpMsgBuf; - size_t buffLen = strlen((const char *)buff); - if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0; - Error("%S: %S! %s", msg, cmd, (char *)lpMsgBuf); -} - -static void Win32CloseProcess(Process *process) { - Win32Process *p = (Win32Process *)process->platform; - if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle); - if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write); - if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read); - if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read); - if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write); - process->is_valid = false; -} - -static void Win32ProcessError(Process *process, String msg, String cmd) { - Win32ReportError(msg, cmd); - Win32CloseProcess(process); -} - -API void WriteStdin(Process *process, String string) { - if (string.len == 0) return; - Assert(process->is_valid); - - Win32Process *p = (Win32Process *)process->platform; - Assert(p->child_stdin_write != INVALID_HANDLE_VALUE); - - DWORD written = 0; - bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0; - - Assert(write_error == false); - Assert(written == string.len); -} - -API void CloseStdin(Process *process) { - Win32Process *p = (Win32Process *)process->platform; - CloseHandle(p->child_stdin_write); - p->child_stdin_write = INVALID_HANDLE_VALUE; -} - -API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { - Process process = {}; - Win32Process *p = (Win32Process *)process.platform; - - Scratch scratch; - command_line = Format(scratch, "/C %S", command_line); - - p->handle = INVALID_HANDLE_VALUE; - p->child_stdout_write = INVALID_HANDLE_VALUE; - p->child_stdout_read = INVALID_HANDLE_VALUE; - p->child_stdin_read = INVALID_HANDLE_VALUE; - p->child_stdin_write = INVALID_HANDLE_VALUE; - - SECURITY_ATTRIBUTES security_atrb = {}; - security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES); - security_atrb.bInheritHandle = TRUE; - - if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - STARTUPINFOW startup = {}; - startup.cb = sizeof(STARTUPINFO); - startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; - startup.hStdInput = p->child_stdin_read; - startup.hStdOutput = p->child_stdout_write; - startup.hStdError = p->child_stdout_write; - startup.wShowWindow = SW_HIDE; - - String16 cwd = ToString16(scratch, working_dir); - String16 cmd = ToString16(scratch, command_line); - - char *env = NULL; - if (enviroment.len) { - Int size = GetSize(enviroment) + enviroment.len + 1; - env = AllocArray(scratch, char, size); - Int i = 0; - For(enviroment) { - MemoryCopy(env + i, it.data, it.len); - i += it.len; - - env[i++] = 0; - } - env[i++] = 0; - } - - DWORD dwCreationFlags = 0; - BOOL bInheritHandles = TRUE; - - PROCESS_INFORMATION info = {}; - if (!CreateProcessW(L"c:\\windows\\system32\\cmd.exe", (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) { - Win32ProcessError(&process, "failed to create process", command_line); - return process; - } - - // Close handles to the stdin and stdout pipes no longer needed by the child process. - // If they are not explicitly closed, there is no way to recognize that the child process has ended. - CloseHandle(info.hThread); - CloseHandle(p->child_stdin_read); - CloseHandle(p->child_stdout_write); - p->child_stdin_read = INVALID_HANDLE_VALUE; - p->child_stdout_write = INVALID_HANDLE_VALUE; - - p->handle = info.hProcess; - process.is_valid = true; - process.id = (int64_t)p->handle; - - if (write_stdin.len) { - WriteStdin(&process, write_stdin); - CloseStdin(&process); - } - - return process; -} - -API bool IsValid(Process *process) { - if (process->is_valid == false) return false; - Win32Process *p = (Win32Process *)process->platform; - - if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) { - return true; - } - - DWORD exit_code; - bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0; - if (get_exit_code_failed) { - exit_code = -1; - } - - process->exit_code = (int)exit_code; - Win32CloseProcess(process); - return false; -} - -API void KillProcess(Process *process) { - Assert(process->is_valid); - Win32Process *p = (Win32Process *)process->platform; - - bool terminate_process_error = TerminateProcess(p->handle, -1) == 0; - if (terminate_process_error) { - Assert(0); - } - Win32CloseProcess(process); - process->exit_code = -1; -} - -API String PollStdout(Allocator allocator, Process *process, bool force_read) { - Assert(process->is_valid); - Win32Process *p = (Win32Process *)process->platform; - - DWORD bytes_avail = 0; - bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0; - if (peek_error) { - return {}; - } else if (bytes_avail == 0) { - return {}; - } - - size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16)); - char *buffer = AllocArray(allocator, char, buffer_size); - - DWORD bytes_read = 0; - bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0; - if (read_error) { - Win32ReportError("Failed to read the stdout of child process", "ReadFile"); - Dealloc(allocator, buffer); - Win32CloseProcess(process); - return {}; - } - - String result = {buffer, bytes_read}; - return result; -} - #elif OS_WASM #include #include @@ -1122,54 +720,6 @@ API FileIter IterateFiles(Allocator alo, String path) { return it; } -API Array SplitCommand(Allocator allocator, String command_line) { - Array cmd = {allocator}; - - String curr = {}; - for (int i = 0; i < command_line.len; i += 1) { - if (command_line.data[i] == ' ') { - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - curr = {}; - } - continue; - } - if (curr.len == 0) { - curr.data = command_line.data + i; - } - curr.len += 1; - } - - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - } - - return cmd; -} - -API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { - return {}; -} - -API bool IsValid(Process *process) { - return false; -} - -API void KillProcess(Process *process) { -} - -API String PollStdout(Allocator allocator, Process *process, bool force_read) { - return {}; -} - -API void WriteStdin(Process *process, String string) { - return; -} - -API void CloseStdin(Process *process) { - return; -} - #endif API double GetTimeSeconds() { diff --git a/src/basic/basic_os.h b/src/basic/basic_os.h index f5ad275..d3d8137 100644 --- a/src/basic/basic_os.h +++ b/src/basic/basic_os.h @@ -33,22 +33,9 @@ String GetWorkingDir(Allocator arena); bool IsAbsolute(String path); int64_t GetFileModTime(String file); -struct Process { - bool is_valid; - int exit_code; - char platform[6 * 8]; - int64_t id; - int64_t view_id; // text editor view - bool scroll_to_end; -}; -Process SpawnProcess(String command_line, String working_dir, String write_stdin = {}, Array enviroment = {}); -bool IsValid(Process *process); -void KillProcess(Process *process); -String PollStdout(Allocator allocator, Process *process, bool force_read); -void WriteStdin(Process *process, String string); -void CloseStdin(Process *process); + double GetTimeMicros(void); enum MakeDirResult { diff --git a/src/text_editor/plugin_build_window.cpp b/src/text_editor/plugin_build_window.cpp index 5403708..4f87e6f 100644 --- a/src/text_editor/plugin_build_window.cpp +++ b/src/text_editor/plugin_build_window.cpp @@ -34,14 +34,18 @@ void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy) { BSet ExecBuild(String windows_cmd, String unix_cmd, String working_dir = ProjectDirectory) { SaveAll(); + Scratch scratch; BSet build = GetBSet(BuildWindowID); BSet main = GetBSet(PrimaryWindowID); SelectRange(build.view, Range{}); ResetBuffer(build.buffer); - if (OS_WINDOWS) { - Exec(build.view->id, true, windows_cmd, working_dir); - } else { - Exec(build.view->id, true, unix_cmd, working_dir); + { + ExecArgs args = ShellArgs(scratch, build.view->id, windows_cmd, working_dir); + args.scroll_to_end = true; + if (!OS_WINDOWS) { + args.cmd = unix_cmd; + } + Exec(args); } main.window->active_goto_list = build.view->id; main.window->goto_list_pos = 0; diff --git a/src/text_editor/plugin_load_vcvars.cpp b/src/text_editor/plugin_load_vcvars.cpp index 0ed4211..6d40d62 100644 --- a/src/text_editor/plugin_load_vcvars.cpp +++ b/src/text_editor/plugin_load_vcvars.cpp @@ -5,7 +5,10 @@ void Windows_SetupVCVarsall(mco_coro *co) { String working_dir = ProjectDirectory; String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-"); String cmd = Format(scratch, "\"%S\" && set", VCVarsPath); - view = ExecHidden(buffer_name, cmd, working_dir); + view = OpenBufferView(buffer_name); + Buffer *buffer = GetBuffer(view->active_buffer); + buffer->dont_try_to_save_in_bulk_ops = true; + Exec(ShellArgs(scratch, view->id, cmd, working_dir)); } for (;;) { if (!ProcessIsActive(view->id)) { @@ -19,6 +22,7 @@ void Windows_SetupVCVarsall(mco_coro *co) { String16 string16 = GetString(buffer); String string = ToString(scratch, string16); Array lines = Split(scratch, string, "\n"); + ProcessEnviroment.len = 0; For (lines) { String s = Trim(it); Array values = Split(scratch, s, "="); @@ -27,8 +31,6 @@ void Windows_SetupVCVarsall(mco_coro *co) { } } - - void LoadVCVars() { RemoveCoroutine("Windows_SetupVCVarsall"); CCtx *co_data = AddCoroutine(Windows_SetupVCVarsall); diff --git a/src/text_editor/plugin_remedybg.cpp b/src/text_editor/plugin_remedybg.cpp index f500250..f8c5aaf 100644 --- a/src/text_editor/plugin_remedybg.cpp +++ b/src/text_editor/plugin_remedybg.cpp @@ -2141,7 +2141,9 @@ bool RDBG_InitConnection(mco_coro *co, bool create_session = true) { String session_name = Format(ctx->arena, "te%llu", GetTimeNanos()); String remedy_string = Format(ctx->arena, "%S --servername %S", RemedyBGPath, session_name); ReportConsolef("Remedybg: %S", remedy_string); - Exec(LogView->id, true, remedy_string, GetPrimaryDirectory()); + ExecArgs args = ShellArgs(ctx->arena, LogView->id, remedy_string, GetPrimaryDirectory()); + args.scroll_to_end = true; + Exec(args); MemoryZero(&RDBG_Ctx, sizeof(RDBG_Ctx)); RDBG_Ctx.cmd.data = command_buf; RDBG_Ctx.cmd.capacity = sizeof(command_buf); diff --git a/src/text_editor/plugin_status_window.cpp b/src/text_editor/plugin_status_window.cpp index 86008fe..c623025 100644 --- a/src/text_editor/plugin_status_window.cpp +++ b/src/text_editor/plugin_status_window.cpp @@ -74,7 +74,7 @@ void UpdateStatusWindow() { commands = Format(scratch, "%S :Reopen", commands); } For (ActiveProcesses) { - if (it.view_id == main.view->id.id) { + if (it.args.output_view == main.view->id) { commands = Format(scratch, "%S :KillProcess", commands); break; } @@ -82,7 +82,7 @@ void UpdateStatusWindow() { String s = Format(scratch, "%S:%lld:%lld %S | :Prev :Next :Close %S", main.buffer->name, (long long)xy.line + 1ll, (long long)xy.col + 1ll, indicators, commands); For (ActiveProcesses) { - if (it.view_id == main.view->id.id) { + if (it.args.output_view == main.view->id) { s = Format(scratch, "%S %lld :KillProcess", s, (long long)it.id); } } diff --git a/src/text_editor/plugin_window_management.cpp b/src/text_editor/plugin_window_management.cpp index 9f17765..5d0cc3f 100644 --- a/src/text_editor/plugin_window_management.cpp +++ b/src/text_editor/plugin_window_management.cpp @@ -49,6 +49,20 @@ void CMD_FocusWindow3() { } } RegisterCommand(CMD_FocusWindow3, "ctrl-3", "Select the 3rd window, counting from left"); +void CMD_FocusWindow4() { + Window *first = GetOverlappingWindow({0,0}); + if (first) { + Window *second = GetOverlappingWindow(GetSideOfWindow(first, DIR_RIGHT)); + if (second) { + Window *third = GetOverlappingWindow(GetSideOfWindow(second, DIR_RIGHT)); + if (third) { + Window *fourth = GetOverlappingWindow(GetSideOfWindow(third, DIR_RIGHT)); + if (fourth) NextActiveWindowID = third->id; + } + } + } +} RegisterCommand(CMD_FocusWindow4, "ctrl-4", "Select the 4th window, counting from left"); + void CMD_NewWindow() { CreateWind(); } RegisterCommand(CMD_NewWindow, "ctrl-backslash", "Creates a new window"); diff --git a/src/text_editor/process.cpp b/src/text_editor/process.cpp index f5ab160..98cb8db 100644 --- a/src/text_editor/process.cpp +++ b/src/text_editor/process.cpp @@ -1,3 +1,456 @@ +#if OS_WINDOWS +struct Win32Process { + HANDLE handle; + HANDLE child_stdout_read; + HANDLE child_stdout_write; + HANDLE child_stdin_read; + HANDLE child_stdin_write; +}; +static_assert(sizeof(Win32Process) < sizeof(Process::platform)); + +static void Win32ReportError(String msg, String cmd) { + LPVOID lpMsgBuf; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); + defer { LocalFree(lpMsgBuf); }; + char *buff = (char *)lpMsgBuf; + size_t buffLen = strlen((const char *)buff); + if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0; + Error("%S: %S! %s", msg, cmd, (char *)lpMsgBuf); +} + +static void Win32CloseProcess(Process *process) { + Win32Process *p = (Win32Process *)process->platform; + if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle); + if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write); + if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read); + if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read); + if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write); + process->is_valid = false; +} + +static void Win32ProcessError(Process *process, String msg, String cmd) { + Win32ReportError(msg, cmd); + Win32CloseProcess(process); +} + +void WriteStdin(Process *process, String string) { + if (string.len == 0) return; + Assert(process->is_valid); + + Win32Process *p = (Win32Process *)process->platform; + Assert(p->child_stdin_write != INVALID_HANDLE_VALUE); + + DWORD written = 0; + bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0; + + Assert(write_error == false); + Assert(written == string.len); +} + +void CloseStdin(Process *process) { + Win32Process *p = (Win32Process *)process->platform; + CloseHandle(p->child_stdin_write); + p->child_stdin_write = INVALID_HANDLE_VALUE; +} + +Process SpawnProcess(ExecArgs args) { + Scratch scratch; + Process process = {}; + process.args = args; + Win32Process *p = (Win32Process *)process.platform; + + p->handle = INVALID_HANDLE_VALUE; + p->child_stdout_write = INVALID_HANDLE_VALUE; + p->child_stdout_read = INVALID_HANDLE_VALUE; + p->child_stdin_read = INVALID_HANDLE_VALUE; + p->child_stdin_write = INVALID_HANDLE_VALUE; + + SECURITY_ATTRIBUTES security_atrb = {}; + security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES); + security_atrb.bInheritHandle = TRUE; + + if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd); + return process; + } + + if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd); + return process; + } + + if (args.open_stdin) { + if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd); + return process; + } + + if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd); + return process; + } + } + + STARTUPINFOW startup = {}; + startup.cb = sizeof(STARTUPINFO); + startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + startup.hStdOutput = p->child_stdout_write; + startup.hStdError = p->child_stdout_write; + startup.wShowWindow = SW_HIDE; + if (args.open_stdin) { + startup.hStdInput = p->child_stdin_read; + } + + String16 cwd = ToString16(scratch, args.cwd); + String16 cmd = ToString16(scratch, args.cmd); + String16 exe = ToString16(scratch, args.exe); + + char *env = NULL; + if (args.env.len) { + Int size = GetSize(args.env) + args.env.len + 1; + env = AllocArray(scratch, char, size); + Int i = 0; + For (args.env) { + MemoryCopy(env + i, it.data, it.len); + i += it.len; + + env[i++] = 0; + } + env[i++] = 0; + } + + DWORD dwCreationFlags = 0; + BOOL bInheritHandles = TRUE; + + PROCESS_INFORMATION info = {}; + if (!CreateProcessW((wchar_t *)exe.data, (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) { + Win32ProcessError(&process, "failed to create process", args.cmd); + return process; + } + + // Close handles to the stdin and stdout pipes no longer needed by the child process. + // If they are not explicitly closed, there is no way to recognize that the child process has ended. + CloseHandle(info.hThread); + CloseHandle(p->child_stdout_write); + p->child_stdout_write = INVALID_HANDLE_VALUE; + + p->handle = info.hProcess; + process.is_valid = true; + process.id = (int64_t)p->handle; + + return process; +} + +bool IsValid(Process *process) { + if (process->is_valid == false) return false; + Win32Process *p = (Win32Process *)process->platform; + + if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) { + return true; + } + + DWORD exit_code; + bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0; + if (get_exit_code_failed) { + exit_code = -1; + } + + process->exit_code = (int)exit_code; + Win32CloseProcess(process); + return false; +} + +void KillProcess(Process *process) { + Assert(process->is_valid); + Win32Process *p = (Win32Process *)process->platform; + + bool terminate_process_error = TerminateProcess(p->handle, -1) == 0; + if (terminate_process_error) { + Assert(0); + } + Win32CloseProcess(process); + process->exit_code = -1; +} + +String PollStdout(Allocator allocator, Process *process, bool force_read) { + Assert(process->is_valid); + Win32Process *p = (Win32Process *)process->platform; + + DWORD bytes_avail = 0; + bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0; + if (peek_error) { + return {}; + } else if (bytes_avail == 0) { + return {}; + } + + size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16)); + char *buffer = AllocArray(allocator, char, buffer_size); + + DWORD bytes_read = 0; + bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0; + if (read_error) { + Win32ReportError("Failed to read the stdout of child process", "ReadFile"); + Dealloc(allocator, buffer); + Win32CloseProcess(process); + return {}; + } + + String result = {buffer, bytes_read}; + return result; +} + +#elif OS_POSIX + +struct UnixProcess { + pid_t pid; + int child_stdout_read; + int stdin_write; +}; + +Array SplitCommand(Allocator allocator, String command_line) { + Array cmd = {allocator}; + + String curr = {}; + for (int i = 0; i < command_line.len; i += 1) { + if (command_line.data[i] == ' ') { + if (curr.len > 0) { + Add(&cmd, Copy(allocator, curr).data); + curr = {}; + } + continue; + } + if (curr.len == 0) { + curr.data = command_line.data + i; + } + curr.len += 1; + } + + if (curr.len > 0) { + Add(&cmd, Copy(allocator, curr).data); + } + + return cmd; +} + +Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { + Scratch scratch; + const int PIPE_READ = 0; + const int PIPE_WRITE = 1; + bool error = false; + + working_dir = Copy(scratch, working_dir); + chdir(working_dir.data); + + Process process = {}; + process.args = args; + UnixProcess *plat = (UnixProcess *)&process.platform; + Array args = SplitCommand(scratch, command_line); + Array env = {scratch}; + + For (enviroment) { + Add(&env, Copy(scratch, it).data); + } + + int stdout_desc[2] = {}; + int stdin_desc[2] = {}; + posix_spawn_file_actions_t actions = {}; + + if (posix_spawn_file_actions_init(&actions) != 0) { + Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno)); + return process; + } + defer { + posix_spawn_file_actions_destroy(&actions); + }; + + if (pipe(stdout_desc) == -1) { + Error("Libc function failed: pipe, with error: %s", strerror(errno)); + return process; + } + defer { + if (error) { + close(stdout_desc[PIPE_READ]); + close(stdout_desc[PIPE_WRITE]); + } else { + close(stdout_desc[PIPE_WRITE]); + } + }; + + if (pipe(stdin_desc) == -1) { + Error("Libc function failed: pipe, with error: %s", strerror(errno)); + return process; + } + defer { + if (error) { + close(stdin_desc[PIPE_READ]); + close(stdin_desc[PIPE_WRITE]); + } else { + close(stdin_desc[PIPE_READ]); + } + }; + + error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno)); + return process; + } + + pid_t process_pid = 0; + error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0; + if (error) { + Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno)); + return process; + } + + + plat->child_stdout_read = stdout_desc[PIPE_READ]; + plat->stdin_write = stdin_desc[PIPE_WRITE]; + plat->pid = process_pid; + + if (write_stdin.len) { + WriteStdin(&process, write_stdin); + CloseStdin(&process); + } + + process.id = process_pid; + process.is_valid = true; + return process; +} + +bool IsValid(Process *process) { + UnixProcess *plat = (UnixProcess *)&process->platform; + if (process->is_valid == false) { + return false; + } + + int status = 0; + pollfd p = {}; + p.fd = plat->child_stdout_read; + p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL; + int res = poll(&p, 1, 0); + if (res > 0) { + pid_t result = waitpid(plat->pid, &status, 0); + Assert(result != -1); + process->exit_code = WEXITSTATUS(status); + return false; + } + + return true; +} + +void KillProcess(Process *process) { + Assert(process->is_valid); + UnixProcess *plat = (UnixProcess *)process->platform; + kill(plat->pid, SIGKILL); + process->exit_code = -1; +} + +String PollStdout(Allocator allocator, Process *process, bool force_read) { + Assert(process->is_valid); + UnixProcess *plat = (UnixProcess *)process->platform; + + String result = {}; + result.data = AllocArray(allocator, char, 16 * 4096); + + pollfd p = {}; + p.fd = plat->child_stdout_read; + p.events = POLLIN; + int res = poll(&p, 1, 0); + if (res > 0 || force_read) { + result.len = read(plat->child_stdout_read, result.data, 4 * 4096); + } + return result; +} + +void WriteStdin(Process *process, String string) { + if (string.len == 0) return; + Assert(process->is_valid); + + UnixProcess *plat = (UnixProcess *)process->platform; + ssize_t size = write(plat->stdin_write, string.data, string.len); + + Assert(size == string.len); +} + +void CloseStdin(Process *process) { + UnixProcess *plat = (UnixProcess *)process->platform; + close(plat->stdin_write); +} + +#else +Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { return {}; } +bool IsValid(Process *process) { return false; } +void KillProcess(Process *process) { } +String PollStdout(Allocator allocator, Process *process, bool force_read) { return {}; } +void WriteStdin(Process *process, String string) { return; } +void CloseStdin(Process *process) { return; } +#endif + +// @todo: perhaps rework the PATH ProcessEnviroment into an object from data_desc +String GetEnv(String name) { + For (ProcessEnviroment) { + if (StartsWith(it, name, true)) { + return it; + } + } + return {}; +} + +String FindPython(Allocator allocator) { + Scratch scratch(allocator); + String path = GetEnv("PATH="); // ignore case on windows so it's fine. 'Path=' not needed + Assert(path.len); + String delim = IF_OS_WINDOWS_ELSE(";", ":"); + Array paths = Split(scratch, path, delim); + String tries[] = {"python3", "python", "py"}; + for (int i = 0; i < Lengthof(tries); i += 1) { + For (paths) { + String path_it = Format(scratch, "%S/%S" IF_OS_WINDOWS_ELSE(".exe", ""), it, tries[i]); + bool is_bad_bad_ms = EndsWith(it, "AppData\\Local\\Microsoft\\WindowsApps") || EndsWith(it, "AppData/Local/Microsoft/WindowsApps"); + if (FileExists(path_it) IF_OS_WINDOWS(&& !is_bad_bad_ms)) { + return Copy(allocator, path_it); + } + } + } + return {}; +} + +void SetShell(Allocator allocator, String *exe, String *cmd, String in_cmd) { +#if OS_WINDOWS + *exe = "c:\\windows\\system32\\cmd.exe"; + *cmd = Format(allocator, "%S /C %S", *exe, in_cmd); +#else + *exe = "/usr/bin/bash"; + String temp_file = WriteTempFile(in_cmd); // @todo: maybe try to pass by stdio here + *cmd = Format(allocator, "%S %S", *exe, temp_file); +#endif +} + // WARNING: seems that this maybe can't work reliably? // in case of 'git grep a' it's possible that child process spawns it's own // child process and then it won't print anything because it won't have @@ -8,11 +461,11 @@ void UpdateProcesses() { IterRemove(ActiveProcesses) { IterRemovePrepare(ActiveProcesses); Scratch scratch; - View *view = GetView(ViewID{it.view_id}); + View *view = GetView(it.args.output_view); String poll = PollStdout(scratch, &it, false); if (poll.len) { - Append(view, poll, it.scroll_to_end); + Append(view, poll, it.args.scroll_to_end); } if (!IsValid(&it)) { ReportConsolef("process %lld exit code = %d", it.id, it.exit_code); @@ -21,20 +474,23 @@ void UpdateProcesses() { } } -void Exec(ViewID view, bool scroll_to_end, String cmd, String working_dir) { - Process process = SpawnProcess(cmd, working_dir, {}, ProcessEnviroment); - ReportConsolef("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, cmd, working_dir); - process.view_id = view.id; - process.scroll_to_end = scroll_to_end; - if (process.is_valid) { - Add(&ActiveProcesses, process); - } +ExecArgs ShellArgs(Allocator allocator, ViewID output_view, String cmd, String working_directory) { + ExecArgs args = {}; + SetShell(allocator, &args.exe, &args.cmd, cmd); + args.env = ProcessEnviroment; + args.cwd = working_directory; + args.output_view = output_view; + args.poll_process = 1; + return args; } -void Exec(ViewID view, bool scroll_to_end, String16 cmd16, String working_dir) { - Scratch scratch; - String cmd = ToString(scratch, cmd16); - Exec(view, scroll_to_end, cmd, working_dir); +Process Exec(ExecArgs args) { + Process process = SpawnProcess(args); + ReportConsolef("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, args.cmd, args.cwd); + if (process.is_valid && args.poll_process) { + Add(&ActiveProcesses, process); + } + return process; } struct ExecResult { @@ -44,9 +500,14 @@ struct ExecResult { ExecResult ExecAndWait(Allocator allocator, String cmd, String working_dir, String stdin_string = {}) { ReportConsolef("ExecAndWait cmd = %S working_dir = %S stdin_string = %S", cmd, working_dir, stdin_string); - Buffer *scratch_buff = CreateScratchBuffer(allocator, 4096 * 4); - Process process = SpawnProcess(cmd, working_dir, stdin_string, ProcessEnviroment); + + ExecArgs args = ShellArgs(allocator, {}, cmd, working_dir); + args.poll_process = 0; + args.open_stdin = 1; + Process process = Exec(args); + WriteStdin(&process, stdin_string); + CloseStdin(&process); for (;IsValid(&process);) { Scratch scratch(allocator); String poll = PollStdout(scratch, &process, true); @@ -60,12 +521,12 @@ void KillProcess(View *view) { IterRemove(ActiveProcesses) { IterRemovePrepare(ActiveProcesses); - ViewID view_id = {it.view_id}; + ViewID view_id = {it.args.output_view}; if (view_id == view->id) { KillProcess(&it); remove_item = true; String string = "process was killed by user\n"; - Append(view, string, it.scroll_to_end); + Append(view, string, it.args.scroll_to_end); // dont break because that will fuck with removal ... } } @@ -73,27 +534,9 @@ void KillProcess(View *view) { bool ProcessIsActive(ViewID view) { For (ActiveProcesses) { - if (it.view_id == view.id) { + if (it.args.output_view == view) { return true; } } return false; } - -View *ExecHidden(String buffer_name, String cmd, String working_dir) { - View *view = OpenBufferView(buffer_name); - Buffer *buffer = GetBuffer(view->active_buffer); - buffer->dont_try_to_save_in_bulk_ops = true; - Exec(view->id, true, cmd, working_dir); - return view; -} - -BSet Exec(String cmd, String working_dir, bool set_active = true) { - BSet main = GetBSet(PrimaryWindowID); - if (set_active) { - NextActiveWindowID = main.window->id; - } - JumpTempBuffer(&main); - Exec(main.view->id, true, cmd, working_dir); - return main; -} diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 2c3cf83..7e4d7ba 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -872,7 +872,18 @@ int main(int argc, char **argv) // return 0; } -#if !OS_WINDOWS +#if OS_WINDOWS + { + wchar_t *p = GetEnvironmentStringsW(); + for (;p && p[0];) { + String16 string16((char16_t *)p); + String string = ToString(Perm, string16); + Add(&ProcessEnviroment, string); + p += string16.len + 1; + } + // FreeEnvironmentStringsW(p); // I get a trap here? why? + } +#else for (int i = 0; environ[i]; i += 1) { Add(&ProcessEnviroment, Copy(Perm, environ[i])); } diff --git a/src/text_editor/text_editor.h b/src/text_editor/text_editor.h index c95a1c5..a6fc8b8 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -45,11 +45,33 @@ enum UIAction { UIAction_No, }; +struct ExecArgs { + String exe; + String cmd; + String cwd; + Array env; + + ViewID output_view; // poll_process + struct { + U32 poll_process : 1; + U32 open_stdin : 1; + U32 scroll_to_end : 1; + }; +}; + +struct Process { + bool is_valid; + int exit_code; + char platform[6 * 8]; + Int id; + + ExecArgs args; // memory for exe and all that is invalid +}; + enum OpenKind { OpenKind_Invalid, OpenKind_Skip, OpenKind_Exec, - OpenKind_BackgroundExec, OpenKind_Goto, OpenKind_Command, #if PLUGIN_CONFIG @@ -68,7 +90,12 @@ struct ResolvedOpen { OpenKind kind; String path; Int line, col; - bool existing_buffer; + + struct { + U32 existing_buffer : 1; + U32 exec_in_background : 1; + U32 use_python_shell : 1; + }; }; struct Buffer { diff --git a/src/text_editor/ui.cpp b/src/text_editor/ui.cpp index 7d770fb..7e548ed 100644 --- a/src/text_editor/ui.cpp +++ b/src/text_editor/ui.cpp @@ -281,7 +281,8 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen // !!exec_hidden if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "!!")) { - result.kind = OpenKind_BackgroundExec; + result.kind = OpenKind_Exec; + result.exec_in_background = 1; result.path = Skip(path, 2); } @@ -289,22 +290,20 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "!")) { result.kind = OpenKind_Exec; result.path = Skip(path, 1); + } - Int idx = 0; - String needle = "{{TEMP}}"; - if (Seek(result.path, needle, &idx, SeekFlag_None)) { - String rest = Skip(result.path, idx + needle.len); - String begin = GetPrefix(result.path, idx); - String temp_filename = WriteTempFile(rest); - result.path = Format(alo, "%S%S", begin, temp_filename); - } + if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "py:")) { + result.kind = OpenKind_Exec; + result.path = Skip(path, 3); + result.use_python_shell = 1; } // https://web bool web = StartsWith(path, "https://") || StartsWith(path, "http://"); if (exec && result.kind == OpenKind_Invalid && web) { result.path = Format(alo, "%S %S", InternetBrowser, path); - result.kind = OpenKind_BackgroundExec; + result.kind = OpenKind_Exec; + result.exec_in_background = 1; } // commit 3kc09as92 @@ -352,7 +351,7 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen Int start = i; String b = {path.data + 1 + start, (end - start)}; - if (At(path, i) == ')') { + if (At(path, i) == '(') { i -= 1; path.len = i + 1; result.line = strtoll(b.data, NULL, 10); @@ -402,7 +401,9 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen String rel_path = Format(alo, "%S/%S", GetDirectory(window), path); existing_buffer = GetBuffer(rel_path, NULL); if (existing_buffer || FileExists(rel_path)) { - result.existing_buffer = existing_buffer; + if (existing_buffer) { + result.existing_buffer = 1; + } result.path = rel_path; result.kind = OpenKind_Goto; } @@ -444,15 +445,28 @@ BSet Open(Window *window, String path, ResolveOpenMeta meta, bool set_active = t } CenterView(window->id); } else if (o.kind == OpenKind_Exec) { - if (set_active) { - NextActiveWindowID = set.window->id; + ExecArgs args = {};// ShellArgs(scratch, LogView->id, o.path, GetPrimaryDirectory()); + args.poll_process = 1; + args.output_view = LogView->id; + args.cwd = GetPrimaryDirectory(); + if (o.exec_in_background == 0) { + if (set_active) { + NextActiveWindowID = set.window->id; + } + JumpTempBuffer(&set); + RawAppend(set.buffer, u"\n"); + args.output_view = set.view->id; } - JumpTempBuffer(&set); - RawAppend(set.buffer, u"\n"); - Exec(set.view->id, false, o.path, GetPrimaryDirectory()); - } else if (o.kind == OpenKind_BackgroundExec) { - // this shouldn't change the focus/window/view - Exec(LogView->id, false, o.path, GetPrimaryDirectory()); + + if (o.use_python_shell == 1) { + args.exe = FindPython(scratch); + String temp_file = WriteTempFile(o.path); + args.cmd = Format(scratch, "%S %S", args.exe, temp_file); + } else { + SetShell(scratch, &args.exe, &args.cmd, o.path); + } + + Exec(args); } else if (o.kind == OpenKind_Command) { EvalCommand(o.path); }