diff --git a/TODOS.md b/TODOS.md index 009b1d5..21bbe96 100644 --- a/TODOS.md +++ b/TODOS.md @@ -24,11 +24,11 @@ 12. Rewrite `write_command_help` to use text/tables -13. Add color flag and support non colored output. +13. Instead of using a writer to strip colors, just don't print the colors. 14. Add a text filter to the multi_select. -15. init -h doesn't show --force flag. +15. init -h doesn't show --force flag. Separate into multiple structs: Global_FLags, and Init_Flags? ## Double-check AI output diff --git a/cli.odin b/cli.odin index 85ee1ef..a201df3 100644 --- a/cli.odin +++ b/cli.odin @@ -22,6 +22,7 @@ Flags :: struct { help: bool `args:"short=h"`, config_file: string `args:"name=config-file,short=c"`, output: Output_Format `args:"short=o"`, + color: Color_Mode, force: bool `args:"short=f"`, } @@ -31,6 +32,12 @@ Output_Format :: enum { JSON, } +Color_Mode :: enum { + Auto, + Always, + Never, +} + CommandInfo :: struct { name: string, usage: string, @@ -95,6 +102,13 @@ parse_args :: proc(args: []string, out: io.Stream, err: io.Stream) -> (cmd: Comm cmd.flags.output = terminal.is_terminal(os.stdout) ? .Table : .JSON } + if cmd.flags.color == .Auto { + cmd.flags.color = terminal.is_terminal(os.stdout) ? .Always : .Never + } + if cmd.flags.color == .Never { + cmd.out = make_ansi_strip_writer(cmd.out) + } + if cmd.flags.config_file == "" { // FIXME: Handle err // TODO: Is this right? @@ -279,6 +293,11 @@ at before, restore your backup with: COLOR_FLAGS + "-o, --output" + ANSI_RESET + " 'table'|'json'", `the format of output data. (default 'table')`, ) + table.row( + &tbl, + COLOR_FLAGS + "--color" + ANSI_RESET + " 'auto'|'always'|'never'", + `Whether or not to colorize output. (default 'auto')`, + ) write_borderless_table(w, &tbl) fmt.wprintf( diff --git a/colors.odin b/colors.odin index f9ba9db..214a6bc 100644 --- a/colors.odin +++ b/colors.odin @@ -1,5 +1,6 @@ package main +import "core:io" import "core:terminal/ansi" COLOR_HEADINGS :: @@ -15,3 +16,71 @@ COLOR_TABLE_HEADING :: ansi.CSI + ansi.FG_BRIGHT_GREEN + ansi.SGR ANSI_RESET :: ansi.CSI + ansi.RESET + ansi.SGR +ANSI_Strip_State :: enum { Normal, GotESC, InCSI } + +ANSI_Strip_Data :: struct { + inner: io.Writer, + state: ANSI_Strip_State, +} + +ansi_strip_proc :: proc( + stream_data: rawptr, + mode: io.Stream_Mode, + p: []byte, + offset: i64, + whence: io.Seek_From, +) -> (n: i64, err: io.Error) { + data := cast(^ANSI_Strip_Data) stream_data + + #partial switch mode { + case .Write: + start := 0 + for i in 0.. start { + io.write(data.inner, p[start:i]) + } + data.state = .GotESC + } + + case .GotESC: + if b == '[' { + data.state = .InCSI + } else { + start = i + data.state = .Normal + } + + case .InCSI: + if b >= 0x40 && b <= 0x7E { + start = i + 1 + data.state = .Normal + } + } + } + + if data.state == .Normal && len(p) > start { + io.write(data.inner, p[start:]) + } + + n = i64(len(p)) + return + + case .Flush: + return 0, io.flush(data.inner) + case .Close: + return 0, io.close(data.inner) + case: + return data.inner.procedure(data.inner.data, mode, p, offset, whence) + } +} + +make_ansi_strip_writer :: proc(inner: io.Writer) -> io.Writer { + data := new(ANSI_Strip_Data, context.temp_allocator) + data.inner = inner + return io.Writer{procedure = ansi_strip_proc, data = rawptr(data)} +}