From 30c1b887414197268bc866aec770786023bd457e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 20 May 2025 15:32:01 -0400 Subject: [PATCH] Add `core:terminal` --- core/terminal/doc.odin | 4 ++ core/terminal/terminal.odin | 104 ++++++++++++++++++++++++++++ core/terminal/terminal_posix.odin | 9 +++ core/terminal/terminal_windows.odin | 9 +++ examples/all/all_main.odin | 3 + 5 files changed, 129 insertions(+) create mode 100644 core/terminal/doc.odin create mode 100644 core/terminal/terminal.odin create mode 100644 core/terminal/terminal_posix.odin create mode 100644 core/terminal/terminal_windows.odin diff --git a/core/terminal/doc.odin b/core/terminal/doc.odin new file mode 100644 index 000000000..490e9d398 --- /dev/null +++ b/core/terminal/doc.odin @@ -0,0 +1,4 @@ +/* +This package is for interacting with the command line interface of the system. +*/ +package terminal diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin new file mode 100644 index 000000000..fae6f880a --- /dev/null +++ b/core/terminal/terminal.odin @@ -0,0 +1,104 @@ +package terminal + +import "core:os" +import "core:strings" + +/* +This describes the range of colors that a terminal is capable of supporting. +*/ +Color_Depth :: enum { + None, // No color support + Three_Bit, // 8 colors + Four_Bit, // 16 colors + Eight_Bit, // 256 colors + True_Color, // 24-bit true color +} + +/* +Returns true if the file `handle` is attached to a terminal. + +This is normally true for `os.stdout` and `os.stderr` unless they are +redirected to a file. +*/ +@(require_results) +is_terminal :: proc(handle: os.Handle) -> bool { + return _is_terminal(handle) +} + +/* +Get the color depth support for the terminal. +*/ +@(require_results) +get_color_depth :: proc() -> Color_Depth { + // Reference documentation: + // + // - [[ https://no-color.org/ ]] + // - [[ https://github.com/termstandard/colors ]] + // - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] + + // Respect `NO_COLOR` above all. + if no_color, ok := os.lookup_env("NO_COLOR"); ok { + defer delete(no_color) + if no_color != "" { + return .None + } + } + + // `COLORTERM` is non-standard but widespread and unambiguous. + if colorterm, ok := os.lookup_env("COLORTERM"); ok { + defer delete(colorterm) + // These are the only values that are typically advertised that have + // anything to do with color depth. + if colorterm == "truecolor" || colorterm == "24bit" { + return .True_Color + } + } + + if term, ok := os.lookup_env("TERM"); ok { + defer delete(term) + if strings.contains(term, "-truecolor") { + return .True_Color + } + if strings.contains(term, "-256color") { + return .Eight_Bit + } + if strings.contains(term, "-16color") { + return .Four_Bit + } + + // The `terminfo` database, which is stored in binary on *nix + // platforms, has an undocumented format that is not guaranteed to be + // portable, so beyond this point, we can only make safe assumptions. + // + // This section should only be necessary for terminals that do not + // define any of the previous environment values. + // + // Only a small sampling of some common values are checked here. + switch term { + case "ansi": fallthrough + case "konsole": fallthrough + case "putty": fallthrough + case "rxvt": fallthrough + case "rxvt-color": fallthrough + case "screen": fallthrough + case "st": fallthrough + case "tmux": fallthrough + case "vte": fallthrough + case "xterm": fallthrough + case "xterm-color": + return .Three_Bit + } + } + + return .None +} + +/* +This is true if the terminal is accepting any form of colored text output. +*/ +color_enabled: bool + +@(init, private) +init_terminal_status :: proc() { + color_enabled = get_color_depth() > .None +} diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin new file mode 100644 index 000000000..adfb6a0da --- /dev/null +++ b/core/terminal/terminal_posix.odin @@ -0,0 +1,9 @@ +#+build linux, darwin, netbsd, openbsd, freebsd, haiku +package terminal + +import "core:os" +import "core:sys/posix" + +_is_terminal :: proc(handle: os.Handle) -> bool { + return bool(posix.isatty(posix.FD(handle))) +} diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin new file mode 100644 index 000000000..caab87cc7 --- /dev/null +++ b/core/terminal/terminal_windows.odin @@ -0,0 +1,9 @@ +package terminal + +import "core:os" +import "core:sys/windows" + +_is_terminal :: proc(handle: os.Handle) -> bool { + mode: windows.DWORD + return bool(windows.GetConsoleMode(windows.HANDLE(handle), &mode)) +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 0a17227b8..97ecfee45 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -129,6 +129,8 @@ import strings "core:strings" import sync "core:sync" import testing "core:testing" +import terminal "core:terminal" + import edit "core:text/edit" import i18n "core:text/i18n" import match "core:text/match" @@ -257,6 +259,7 @@ _ :: strconv _ :: strings _ :: sync _ :: testing +_ :: terminal _ :: scanner _ :: i18n _ :: match