1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package terminal 16 17import ( 18 "bytes" 19 "io" 20 "os" 21 "syscall" 22 "unsafe" 23) 24 25func isTerminal(w io.Writer) bool { 26 if f, ok := w.(*os.File); ok { 27 var termios syscall.Termios 28 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), 29 ioctlGetTermios, uintptr(unsafe.Pointer(&termios)), 30 0, 0, 0) 31 return err == 0 32 } 33 return false 34} 35 36func termWidth(w io.Writer) (int, bool) { 37 if f, ok := w.(*os.File); ok { 38 var winsize struct { 39 ws_row, ws_column uint16 40 ws_xpixel, ws_ypixel uint16 41 } 42 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), 43 syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)), 44 0, 0, 0) 45 return int(winsize.ws_column), err == 0 46 } 47 return 0, false 48} 49 50// stripAnsiEscapes strips ANSI control codes from a byte array in place. 51func stripAnsiEscapes(input []byte) []byte { 52 // read represents the remaining part of input that needs to be processed. 53 read := input 54 // write represents where we should be writing in input. 55 // It will share the same backing store as input so that we make our modifications 56 // in place. 57 write := input 58 59 // advance will copy count bytes from read to write and advance those slices 60 advance := func(write, read []byte, count int) ([]byte, []byte) { 61 copy(write, read[:count]) 62 return write[count:], read[count:] 63 } 64 65 for { 66 // Find the next escape sequence 67 i := bytes.IndexByte(read, 0x1b) 68 // If it isn't found, or if there isn't room for <ESC>[, finish 69 if i == -1 || i+1 >= len(read) { 70 copy(write, read) 71 break 72 } 73 74 // Not a CSI code, continue searching 75 if read[i+1] != '[' { 76 write, read = advance(write, read, i+1) 77 continue 78 } 79 80 // Found a CSI code, advance up to the <ESC> 81 write, read = advance(write, read, i) 82 83 // Find the end of the CSI code 84 i = bytes.IndexFunc(read, func(r rune) bool { 85 return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') 86 }) 87 if i == -1 { 88 // We didn't find the end of the code, just remove the rest 89 i = len(read) - 1 90 } 91 92 // Strip off the end marker too 93 i = i + 1 94 95 // Skip the reader forward and reduce final length by that amount 96 read = read[i:] 97 input = input[:len(input)-i] 98 } 99 100 return input 101} 102