github.com/richardwilkes/toolbox@v1.121.0/xio/term/ansi.go (about)

     1  // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, version 2.0. If a copy of the MPL was not distributed with
     5  // this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  //
     7  // This Source Code Form is "Incompatible With Secondary Licenses", as
     8  // defined by the Mozilla Public License, version 2.0.
     9  
    10  package term
    11  
    12  import (
    13  	"fmt"
    14  	"io"
    15  )
    16  
    17  // ANSI color constants
    18  const (
    19  	Black Color = iota
    20  	Red
    21  	Green
    22  	Yellow
    23  	Blue
    24  	Magenta
    25  	Cyan
    26  	White
    27  )
    28  
    29  // ANSI style constants. Multiple styles may be or'd together.
    30  const (
    31  	Bold Style = 1 << iota
    32  	Underline
    33  	Blink
    34  	Normal = 0
    35  )
    36  
    37  // Color represents an ANSI terminal color
    38  type Color int
    39  
    40  // Style represents an ANSI terminal style. Multiple styles may be or'd together.
    41  type Style int
    42  
    43  // ANSI provides support for ANSI terminal escape sequences.
    44  type ANSI struct {
    45  	out io.Writer
    46  	ok  bool
    47  }
    48  
    49  // NewANSI creates a new ANSI terminal and attaches it to 'out'.
    50  func NewANSI(out io.Writer) *ANSI {
    51  	return &ANSI{out: out, ok: IsTerminal(out)}
    52  }
    53  
    54  // Bell causes the bell to sound.
    55  func (a *ANSI) Bell() {
    56  	fmt.Fprint(a, "\007")
    57  }
    58  
    59  // Reset colors and styles.
    60  func (a *ANSI) Reset() {
    61  	if a.ok {
    62  		fmt.Fprint(a, "\033[m")
    63  	}
    64  }
    65  
    66  // Up moves the cursor up 'count' rows. If this would put it beyond the top edge of the screen, it will instead go to
    67  // the top edge of the screen.
    68  func (a *ANSI) Up(count int) {
    69  	if a.ok {
    70  		fmt.Fprintf(a, "\033[%dA", count)
    71  	}
    72  }
    73  
    74  // Down moves the cursor down 'count' rows. If this would put it beyond the bottom edge of the screen, it will instead
    75  // go to the bottom edge of the screen.
    76  func (a *ANSI) Down(count int) {
    77  	if a.ok {
    78  		fmt.Fprintf(a, "\033[%dB", count)
    79  	}
    80  }
    81  
    82  // Left moves the cursor left 'count' columns. If this would put it beyond the left edge of the screen, it will instead
    83  // go to the left edge of the screen.
    84  func (a *ANSI) Left(count int) {
    85  	if a.ok {
    86  		fmt.Fprintf(a, "\033[%dD", count)
    87  	}
    88  }
    89  
    90  // Right moves the cursor right 'count' columns. If this would put it beyond the right edge of the screen, it will
    91  // instead go to the right edge of the screen.
    92  func (a *ANSI) Right(count int) {
    93  	if a.ok {
    94  		fmt.Fprintf(a, "\033[%dC", count)
    95  	}
    96  }
    97  
    98  // Position the cursor at 'row' and 'column'. Both values are 1-based.
    99  func (a *ANSI) Position(row, column int) {
   100  	if a.ok {
   101  		fmt.Fprintf(a, "\033[%d;%dH", row, column)
   102  	}
   103  }
   104  
   105  // Clear the screen and position the cursor at row 1, column 1.
   106  func (a *ANSI) Clear() {
   107  	if a.ok {
   108  		fmt.Fprint(a, "\033[2J")
   109  	}
   110  }
   111  
   112  // ClearToStart clears the screen from the cursor to the beginning of the screen.
   113  func (a *ANSI) ClearToStart() {
   114  	if a.ok {
   115  		fmt.Fprint(a, "\033[1J")
   116  	}
   117  }
   118  
   119  // ClearToEnd clears the screen from the cursor to the end of the screen.
   120  func (a *ANSI) ClearToEnd() {
   121  	if a.ok {
   122  		fmt.Fprint(a, "\033[J")
   123  	}
   124  }
   125  
   126  // EraseLine clears the current row.
   127  func (a *ANSI) EraseLine() {
   128  	if a.ok {
   129  		fmt.Fprint(a, "\033[2K")
   130  	}
   131  }
   132  
   133  // EraseLineToStart clears from the cursor position to the start of the current row.
   134  func (a *ANSI) EraseLineToStart() {
   135  	if a.ok {
   136  		fmt.Fprint(a, "\033[1K")
   137  	}
   138  }
   139  
   140  // EraseLineToEnd clears from the cursor position to the end of the current row.
   141  func (a *ANSI) EraseLineToEnd() {
   142  	if a.ok {
   143  		fmt.Fprint(a, "\033[K")
   144  	}
   145  }
   146  
   147  // SavePosition saves the current cursor position.
   148  func (a *ANSI) SavePosition() {
   149  	if a.ok {
   150  		fmt.Fprint(a, "\033[s")
   151  	}
   152  }
   153  
   154  // RestorePosition restores the previously saved cursor position.
   155  func (a *ANSI) RestorePosition() {
   156  	if a.ok {
   157  		fmt.Fprint(a, "\033[u")
   158  	}
   159  }
   160  
   161  // HideCursor makes the cursor invisible.
   162  func (a *ANSI) HideCursor() {
   163  	if a.ok {
   164  		fmt.Fprint(a, "\033[?25l")
   165  	}
   166  }
   167  
   168  // ShowCursor makes the cursor visible.
   169  func (a *ANSI) ShowCursor() {
   170  	if a.ok {
   171  		fmt.Fprint(a, "\033[?25h")
   172  	}
   173  }
   174  
   175  // Foreground sets the foreground color and style for subsequent output.
   176  func (a *ANSI) Foreground(color Color, style Style) {
   177  	if a.ok {
   178  		fmt.Fprint(a, "\033[0;")
   179  		if style&Bold == Bold {
   180  			fmt.Fprint(a, "1;")
   181  		}
   182  		if style&Underline == Underline {
   183  			fmt.Fprint(a, "4;")
   184  		}
   185  		if style&Blink == Blink {
   186  			fmt.Fprint(a, "5;")
   187  		}
   188  		fmt.Fprintf(a, "%dm", 30+color)
   189  	}
   190  }
   191  
   192  // Background sets the background color for subsequent output.
   193  func (a *ANSI) Background(color Color) {
   194  	if a.ok {
   195  		fmt.Fprintf(a, "\033[%dm", 40+color)
   196  	}
   197  }
   198  
   199  // Write implements the io.Writer interface.
   200  func (a *ANSI) Write(p []byte) (n int, err error) {
   201  	return a.out.Write(p)
   202  }