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			wsRow, wsColumn    uint16
45			wsXpixel, wsYpixel 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.wsColumn), int(winsize.wsRow), 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