| // Copyright 2017 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package terminal |
| |
| import ( |
| "bytes" |
| "io" |
| "os" |
| "syscall" |
| "unsafe" |
| ) |
| |
| func isSmartTerminal(w io.Writer) bool { |
| if f, ok := w.(*os.File); ok { |
| if term, ok := os.LookupEnv("TERM"); ok && term == "dumb" { |
| return false |
| } |
| var termios syscall.Termios |
| _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), |
| ioctlGetTermios, uintptr(unsafe.Pointer(&termios)), |
| 0, 0, 0) |
| return err == 0 |
| } else if _, ok := w.(*fakeSmartTerminal); ok { |
| return true |
| } |
| return false |
| } |
| |
| func termSize(w io.Writer) (width int, height int, ok bool) { |
| if f, ok := w.(*os.File); ok { |
| var winsize struct { |
| wsRow, wsColumn uint16 |
| wsXpixel, wsYpixel uint16 |
| } |
| _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), |
| syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)), |
| 0, 0, 0) |
| return int(winsize.wsColumn), int(winsize.wsRow), err == 0 |
| } else if f, ok := w.(*fakeSmartTerminal); ok { |
| return f.termWidth, f.termHeight, true |
| } |
| return 0, 0, false |
| } |
| |
| // stripAnsiEscapes strips ANSI control codes from a byte array in place. |
| func stripAnsiEscapes(input []byte) []byte { |
| // read represents the remaining part of input that needs to be processed. |
| read := input |
| // write represents where we should be writing in input. |
| // It will share the same backing store as input so that we make our modifications |
| // in place. |
| write := input |
| |
| // advance will copy count bytes from read to write and advance those slices |
| advance := func(write, read []byte, count int) ([]byte, []byte) { |
| copy(write, read[:count]) |
| return write[count:], read[count:] |
| } |
| |
| for { |
| // Find the next escape sequence |
| i := bytes.IndexByte(read, 0x1b) |
| // If it isn't found, or if there isn't room for <ESC>[, finish |
| if i == -1 || i+1 >= len(read) { |
| copy(write, read) |
| break |
| } |
| |
| // Not a CSI code, continue searching |
| if read[i+1] != '[' { |
| write, read = advance(write, read, i+1) |
| continue |
| } |
| |
| // Found a CSI code, advance up to the <ESC> |
| write, read = advance(write, read, i) |
| |
| // Find the end of the CSI code |
| i = bytes.IndexFunc(read, func(r rune) bool { |
| return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') |
| }) |
| if i == -1 { |
| // We didn't find the end of the code, just remove the rest |
| i = len(read) - 1 |
| } |
| |
| // Strip off the end marker too |
| i = i + 1 |
| |
| // Skip the reader forward and reduce final length by that amount |
| read = read[i:] |
| input = input[:len(input)-i] |
| } |
| |
| return input |
| } |
| |
| type fakeSmartTerminal struct { |
| bytes.Buffer |
| termWidth, termHeight int |
| } |