mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-bin-mirb: add OSC 11 terminal background color detection
Automatically detect terminal background color using OSC 11 escape sequence to select appropriate syntax highlighting theme (dark/light). Detection priority: MIRB_THEME env > OSC 11 > COLORFGBG env > dark default. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
|
||||
#include "mirb_editor.h"
|
||||
#include "mirb_completion.h"
|
||||
#include "mirb_highlight.h"
|
||||
|
||||
/* obsolete configuration */
|
||||
#ifdef DISABLE_MIRB_UNDERSCORE
|
||||
@@ -520,6 +521,11 @@ main(int argc, char **argv)
|
||||
mrb_define_global_const(mrb, "ARGV", ARGV);
|
||||
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$DEBUG"), mrb_bool_value(args.debug));
|
||||
|
||||
/* Query terminal background color before any output */
|
||||
if (isatty(fileno(stdin)) && isatty(fileno(stdout))) {
|
||||
mirb_highlight_query_terminal();
|
||||
}
|
||||
|
||||
print_hint();
|
||||
|
||||
cxt = mrb_ccontext_new(mrb);
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
|
||||
#include "mirb_highlight.h"
|
||||
#include "mirb_term.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
@@ -140,35 +142,66 @@ print_colored(mirb_highlighter *hl, const char *start, size_t len, mirb_token_ty
|
||||
if (*color) printf("%s", reset);
|
||||
}
|
||||
|
||||
/* Cached terminal background color from pre-query */
|
||||
static mirb_bg_color cached_bg_color = MIRB_BG_UNKNOWN;
|
||||
static mrb_bool bg_color_queried = FALSE;
|
||||
|
||||
/*
|
||||
* Detect theme from environment
|
||||
* Pre-query terminal background color
|
||||
* Must be called before any output to avoid response appearing on screen
|
||||
*/
|
||||
void
|
||||
mirb_highlight_query_terminal(void)
|
||||
{
|
||||
if (!bg_color_queried) {
|
||||
cached_bg_color = mirb_term_query_bg_color(500); /* 500ms timeout */
|
||||
bg_color_queried = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect theme from terminal background color
|
||||
*
|
||||
* Priority:
|
||||
* 1. MIRB_THEME environment variable (explicit override)
|
||||
* 2. Cached OSC 11 result (from mirb_highlight_query_terminal)
|
||||
* 3. COLORFGBG environment variable (rxvt, some xterm)
|
||||
* 4. Default to dark theme
|
||||
*/
|
||||
mirb_theme
|
||||
mirb_highlight_detect_theme(void)
|
||||
{
|
||||
const char *env;
|
||||
|
||||
/* Check explicit MIRB_THEME first */
|
||||
/* 1. Check explicit MIRB_THEME first (user override) */
|
||||
env = getenv("MIRB_THEME");
|
||||
if (env) {
|
||||
if (strcmp(env, "light") == 0) return MIRB_THEME_LIGHT;
|
||||
if (strcmp(env, "dark") == 0) return MIRB_THEME_DARK;
|
||||
}
|
||||
|
||||
/* Check COLORFGBG (format: "fg;bg" where bg > 6 usually means light) */
|
||||
/* 2. Use cached OSC 11 result (must call mirb_highlight_query_terminal first) */
|
||||
if (cached_bg_color == MIRB_BG_LIGHT) return MIRB_THEME_LIGHT;
|
||||
if (cached_bg_color == MIRB_BG_DARK) return MIRB_THEME_DARK;
|
||||
|
||||
/* 3. Check COLORFGBG (format: "fg;bg" where bg > 6 usually means light) */
|
||||
env = getenv("COLORFGBG");
|
||||
if (env) {
|
||||
const char *semi = strchr(env, ';');
|
||||
if (semi) {
|
||||
int bg = atoi(semi + 1);
|
||||
int bg_color = atoi(semi + 1);
|
||||
/* Background colors 7, 15, or high values typically mean light theme */
|
||||
if (bg == 7 || bg == 15 || (bg >= 230 && bg <= 255)) {
|
||||
if (bg_color == 7 || bg_color == 15 || (bg_color >= 230 && bg_color <= 255)) {
|
||||
return MIRB_THEME_LIGHT;
|
||||
}
|
||||
/* Low values (0-6, 8) typically mean dark theme */
|
||||
if (bg_color <= 8) {
|
||||
return MIRB_THEME_DARK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Default to dark theme (more common in terminals) */
|
||||
/* 4. Default to dark theme (more common in terminals) */
|
||||
return MIRB_THEME_DARK;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,11 +59,17 @@ void mirb_highlight_init(mirb_highlighter *hl, mrb_bool enabled);
|
||||
void mirb_highlight_set_theme(mirb_highlighter *hl, mirb_theme theme);
|
||||
|
||||
/*
|
||||
* Detect theme from environment variables
|
||||
* Detect theme from environment variables and terminal query
|
||||
* Returns MIRB_THEME_DARK if cannot detect
|
||||
*/
|
||||
mirb_theme mirb_highlight_detect_theme(void);
|
||||
|
||||
/*
|
||||
* Pre-query terminal background color (call before any output)
|
||||
* This caches the result for later use by mirb_highlight_detect_theme()
|
||||
*/
|
||||
void mirb_highlight_query_terminal(void);
|
||||
|
||||
/*
|
||||
* Print a line with syntax highlighting
|
||||
* Handles multi-line strings/comments by tracking state
|
||||
|
||||
@@ -348,3 +348,144 @@ mirb_term_clear_below(void)
|
||||
{
|
||||
printf("\033[J");
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
/*
|
||||
* Query terminal background color using OSC 11 escape sequence
|
||||
*
|
||||
* Protocol:
|
||||
* Send: ESC ] 11 ; ? ESC \ (or BEL instead of ESC \)
|
||||
* Recv: ESC ] 11 ; rgb:RRRR/GGGG/BBBB ESC \
|
||||
*
|
||||
* The response uses 16-bit color values (0000-FFFF per component).
|
||||
* Some terminals use 8-bit (00-FF) format instead.
|
||||
*/
|
||||
mirb_bg_color
|
||||
mirb_term_query_bg_color(int timeout_ms)
|
||||
{
|
||||
struct termios old_term, new_term;
|
||||
char buf[64];
|
||||
size_t total = 0;
|
||||
ssize_t n;
|
||||
int r, g, b;
|
||||
mirb_bg_color result = MIRB_BG_UNKNOWN;
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
const char *p;
|
||||
|
||||
/* Must be a terminal */
|
||||
if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
|
||||
return MIRB_BG_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Save original terminal settings */
|
||||
if (tcgetattr(STDIN_FILENO, &old_term) < 0) {
|
||||
return MIRB_BG_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Flush any pending input first */
|
||||
tcflush(STDIN_FILENO, TCIFLUSH);
|
||||
|
||||
/* Disable echo and canonical mode for raw read */
|
||||
new_term = old_term;
|
||||
new_term.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL);
|
||||
new_term.c_iflag &= ~(IXON | IXOFF | ICRNL);
|
||||
new_term.c_cc[VMIN] = 0;
|
||||
new_term.c_cc[VTIME] = 0;
|
||||
|
||||
/* TCSAFLUSH: flush I/O and apply settings */
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_term) < 0) {
|
||||
return MIRB_BG_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Wait for terminal settings to take effect */
|
||||
tcdrain(STDOUT_FILENO);
|
||||
|
||||
/* Send OSC 11 query: ESC ] 11 ; ? ESC \ */
|
||||
if (write(STDOUT_FILENO, "\033]11;?\033\\", 8) != 8) {
|
||||
goto restore;
|
||||
}
|
||||
/* Ensure query is sent to terminal */
|
||||
tcdrain(STDOUT_FILENO);
|
||||
/* Give terminal time to process and respond */
|
||||
usleep(50000); /* 50ms */
|
||||
|
||||
/* Read response with timeout - loop to get complete response */
|
||||
while (total < sizeof(buf) - 1) {
|
||||
int sel;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
sel = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
|
||||
if (sel < 0) {
|
||||
if (errno == EINTR) continue; /* Retry on signal interrupt */
|
||||
break; /* Other error */
|
||||
}
|
||||
if (sel == 0) break; /* Timeout */
|
||||
|
||||
n = read(STDIN_FILENO, buf + total, sizeof(buf) - 1 - total);
|
||||
if (n <= 0) {
|
||||
if (n < 0 && errno == EINTR) continue; /* Retry on signal interrupt */
|
||||
break;
|
||||
}
|
||||
total += (size_t)n;
|
||||
|
||||
/* Check for terminator: ESC \ (0x1b 0x5c) or BEL (0x07) */
|
||||
if (total >= 2 && buf[total-2] == '\033' && buf[total-1] == '\\') break;
|
||||
if (total >= 1 && buf[total-1] == '\007') break;
|
||||
|
||||
/* Reduce timeout for subsequent reads */
|
||||
timeout_ms = 10;
|
||||
}
|
||||
|
||||
if (total == 0) {
|
||||
goto restore;
|
||||
}
|
||||
buf[total] = '\0';
|
||||
|
||||
/* Parse response: look for "rgb:" followed by hex values */
|
||||
p = strstr(buf, "rgb:");
|
||||
if (p) {
|
||||
p += 4;
|
||||
/* Try 16-bit format: rgb:RRRR/GGGG/BBBB */
|
||||
if (sscanf(p, "%4x/%4x/%4x", &r, &g, &b) == 3) {
|
||||
/* Normalize to 8-bit range */
|
||||
r >>= 8; g >>= 8; b >>= 8;
|
||||
}
|
||||
/* Try 8-bit format: rgb:RR/GG/BB */
|
||||
else if (sscanf(p, "%2x/%2x/%2x", &r, &g, &b) == 3) {
|
||||
/* Already 8-bit */
|
||||
}
|
||||
else {
|
||||
goto restore;
|
||||
}
|
||||
|
||||
/* Calculate relative luminance (ITU-R BT.709 simplified) */
|
||||
/* Y = 0.2126*R + 0.7152*G + 0.0722*B */
|
||||
/* Scale: 0-255 input, threshold at ~127.5 */
|
||||
int luminance = (2126 * r + 7152 * g + 722 * b) / 10000;
|
||||
result = (luminance < 128) ? MIRB_BG_DARK : MIRB_BG_LIGHT;
|
||||
}
|
||||
|
||||
restore:
|
||||
/* Flush any remaining input before restoring terminal */
|
||||
tcflush(STDIN_FILENO, TCIFLUSH);
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
|
||||
return result;
|
||||
}
|
||||
|
||||
#else /* Windows */
|
||||
|
||||
mirb_bg_color
|
||||
mirb_term_query_bg_color(int timeout_ms)
|
||||
{
|
||||
(void)timeout_ms;
|
||||
/* Windows Terminal supports OSC 11, but implementation requires
|
||||
* different I/O handling. For now, return unknown and rely on
|
||||
* fallback detection methods. */
|
||||
return MIRB_BG_UNKNOWN;
|
||||
}
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
@@ -118,4 +118,20 @@ void mirb_term_get_size(mirb_term *term);
|
||||
*/
|
||||
void mirb_term_flush(void);
|
||||
|
||||
/*
|
||||
* Background color detection result
|
||||
*/
|
||||
typedef enum mirb_bg_color {
|
||||
MIRB_BG_UNKNOWN, /* could not detect */
|
||||
MIRB_BG_DARK, /* dark background (luminance < 0.5) */
|
||||
MIRB_BG_LIGHT /* light background (luminance >= 0.5) */
|
||||
} mirb_bg_color;
|
||||
|
||||
/*
|
||||
* Query terminal background color using OSC 11
|
||||
* Returns MIRB_BG_UNKNOWN if terminal doesn't respond within timeout
|
||||
* timeout_ms: timeout in milliseconds (recommended: 100)
|
||||
*/
|
||||
mirb_bg_color mirb_term_query_bg_color(int timeout_ms);
|
||||
|
||||
#endif /* MIRB_TERM_H */
|
||||
|
||||
Reference in New Issue
Block a user