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 isSmartTerminal(w io.Writer) bool { 26 if f, ok := w.(*os.File); ok { 27 if term, ok := os.LookupEnv("TERM"); ok && term == "dumb" { 28 return false 29 } 30 var termios syscall.Termios 31 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), 32 ioctlGetTermios, uintptr(unsafe.Pointer(&termios)), 33 0, 0, 0) 34 return err == 0 35 } else if _, ok := w.(*fakeSmartTerminal); ok { 36 return true 37 } 38 return false 39} 40 41func termSize(w io.Writer) (width int, height int, ok bool) { 42 if f, ok := w.(*os.File); ok { 43 var winsize struct { 44 ws_row, ws_column uint16 45 ws_xpixel, ws_ypixel uint16 46 } 47 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), 48 syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)), 49 0, 0, 0) 50 return int(winsize.ws_column), int(winsize.ws_row), err == 0 51 } else if f, ok := w.(*fakeSmartTerminal); ok { 52 return f.termWidth, f.termHeight, true 53 } 54 return 0, 0, false 55} 56 57// stripAnsiEscapes strips ANSI control codes from a byte array in place. 58func stripAnsiEscapes(input []byte) []byte { 59 // read represents the remaining part of input that needs to be processed. 60 read := input 61 // write represents where we should be writing in input. 62 // It will share the same backing store as input so that we make our modifications 63 // in place. 64 write := input 65 66 // advance will copy count bytes from read to write and advance those slices 67 advance := func(write, read []byte, count int) ([]byte, []byte) { 68 copy(write, read[:count]) 69 return write[count:], read[count:] 70 } 71 72 for { 73 // Find the next escape sequence 74 i := bytes.IndexByte(read, 0x1b) 75 // If it isn't found, or if there isn't room for <ESC>[, finish 76 if i == -1 || i+1 >= len(read) { 77 copy(write, read) 78 break 79 } 80 81 // Not a CSI code, continue searching 82 if read[i+1] != '[' { 83 write, read = advance(write, read, i+1) 84 continue 85 } 86 87 // Found a CSI code, advance up to the <ESC> 88 write, read = advance(write, read, i) 89 90 // Find the end of the CSI code 91 i = bytes.IndexFunc(read, func(r rune) bool { 92 return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') 93 }) 94 if i == -1 { 95 // We didn't find the end of the code, just remove the rest 96 i = len(read) - 1 97 } 98 99 // Strip off the end marker too 100 i = i + 1 101 102 // Skip the reader forward and reduce final length by that amount 103 read = read[i:] 104 input = input[:len(input)-i] 105 } 106 107 return input 108} 109 110type fakeSmartTerminal struct { 111 bytes.Buffer 112 termWidth, termHeight int 113} 114