<commit_before>// +build !windows,!plan9

package term

import (
	"os"
	"testing"
	"time"

	"github.com/elves/elvish/edit/ui"
)

func setupTest() (r, w *os.File, rd Reader) {
	r, w, err := os.Pipe()
	if err != nil {
		panic(err)
	}
	return r, w, NewReader(r)
}

var keyTests = []struct {
	input string
	want  Event
}{
	// Simple graphical key.
	{"x", KeyEvent{'x', 0}},
	{"X", KeyEvent{'X', 0}},
	{" ", KeyEvent{' ', 0}},

	// Ctrl key.
	{"\001", KeyEvent{'A', ui.Ctrl}},
	{"\033", KeyEvent{'[', ui.Ctrl}},

	// Ctrl-ish keys, but not thought as Ctrl keys by our reader.
	{"\n", KeyEvent{'\n', 0}},
	{"\t", KeyEvent{'\t', 0}},
	{"\x7f", KeyEvent{'\x7f', 0}}, // backspace

	// Alt plus simple graphical key.
	{"\033a", KeyEvent{'a', ui.Alt}},
	{"\033[", KeyEvent{'[', ui.Alt}},

	// G3-style key.
	{"\033OA", KeyEvent{ui.Up, 0}},
	{"\033OH", KeyEvent{ui.Home, 0}},

	// CSI-sequence key identified by the ending rune.
	{"\033[A", KeyEvent{ui.Up, 0}},
	{"\033[H", KeyEvent{ui.Home, 0}},
	// Modifiers.
	{"\033[1;1A", KeyEvent{ui.Up, 0}},
	{"\033[1;2A", KeyEvent{ui.Up, ui.Shift}},
	{"\033[1;3A", KeyEvent{ui.Up, ui.Alt}},
	{"\033[1;4A", KeyEvent{ui.Up, ui.Shift | ui.Alt}},
	{"\033[1;5A", KeyEvent{ui.Up, ui.Ctrl}},
	{"\033[1;6A", KeyEvent{ui.Up, ui.Shift | ui.Ctrl}},
	{"\033[1;7A", KeyEvent{ui.Up, ui.Alt | ui.Ctrl}},
	{"\033[1;8A", KeyEvent{ui.Up, ui.Shift | ui.Alt | ui.Ctrl}},
	// The modifiers below should be for Meta, but we conflate Alt and Meta.
	{"\033[1;9A", KeyEvent{ui.Up, ui.Alt}},
	{"\033[1;10A", KeyEvent{ui.Up, ui.Shift | ui.Alt}},
	{"\033[1;11A", KeyEvent{ui.Up, ui.Alt}},
	{"\033[1;12A", KeyEvent{ui.Up, ui.Shift | ui.Alt}},
	{"\033[1;13A", KeyEvent{ui.Up, ui.Alt | ui.Ctrl}},
	{"\033[1;14A", KeyEvent{ui.Up, ui.Shift | ui.Alt | ui.Ctrl}},
	{"\033[1;15A", KeyEvent{ui.Up, ui.Alt | ui.Ctrl}},
	{"\033[1;16A", KeyEvent{ui.Up, ui.Shift | ui.Alt | ui.Ctrl}},

	// CSI-sequence key with one argument, always ending in '~'.
	{"\033[1~", KeyEvent{ui.Home, 0}},
	{"\033[11~", KeyEvent{ui.F1, 0}},

	// CSI-sequence key with three arguments and ending in '~'. The first
	// argument is always 27, the second identifies the modifier and the last
	// identifies the key.
	{"\033[27;4;63~", KeyEvent{';', ui.Shift | ui.Alt}},
}

func TestReader_ReadKeys(t *testing.T) {
	r, w, reader := setupTest()
	defer r.Close()
	defer w.Close()
	reader.Start()
	defer reader.Close()
	defer reader.Stop()

	for _, test := range keyTests {
		w.WriteString(test.input)
		select {
		case event := <-reader.EventChan():
			if event != test.want {
				t.Errorf("Reader reads event %v, want %v", event, test.want)
			}
		case <-time.After(time.Second):
			t.Errorf("Reader timed out")
		}
	}
}

func TestReader_StopMakesUnderlyingFileAvailable(t *testing.T) {
	r, w, reader := setupTest()
	defer r.Close()
	defer w.Close()
	reader.Start()
	defer reader.Close()

	// tests that after calling Stop, the
	// Reader no longer attempts to read from the underlying file, so it is
	// available for use by others.
	reader.Stop()

	// Verify that the reader has indeed stopped: write something via w,
	// and get it back via r.
	written := "lorem ipsum"
	w.WriteString(written)
	var buf [32]byte
	nr, err := r.Read(buf[:])
	if err != nil {
		panic(err)
	}
	got := string(buf[:nr])
	if got != written {
		t.Errorf("got %q, want %q", got, written)
	}
}

func TestReader_StartAfterStopIndeedStarts(t *testing.T) {
	r, w, reader := setupTest()
	defer r.Close()
	defer w.Close()
	reader.Start()
	defer reader.Close()
	defer reader.Stop()

	for i := 0; i < 100; i++ {
		// Test that calling Start very shortly after Stop puts the Reader
		// in the correct started state. This test is for ensuring that the
		// operations do not have race conditions.
		reader.Stop()
		reader.Start()

		w.WriteString("a")
		select {
		case event := <-reader.EventChan():
			wantEvent := K('a')
			if event != wantEvent {
				t.Errorf("After Stop and Start, Reader reads %v, want %v", event, wantEvent)
			}
		case <-time.After(time.Second):
			t.Errorf("After Stop and Start, Reader timed out")
		}
	}
}
<commit_msg>cli/term: Use the K function to construct key events.<commit_after>// +build !windows,!plan9

package term

import (
	"os"
	"testing"
	"time"

	"github.com/elves/elvish/edit/ui"
)

func setupTest() (r, w *os.File, rd Reader) {
	r, w, err := os.Pipe()
	if err != nil {
		panic(err)
	}
	return r, w, NewReader(r)
}

var keyTests = []struct {
	input string
	want  Event
}{
	// Simple graphical key.
	{"x", K('x')},
	{"X", K('X')},
	{" ", K(' ')},

	// Ctrl key.
	{"\001", K('A', ui.Ctrl)},
	{"\033", K('[', ui.Ctrl)},

	// Ambiguous Ctrl keys; the reader uses the non-Ctrl form as canonical.
	{"\n", K('\n')},
	{"\t", K('\t')},
	{"\x7f", K('\x7f')}, // backspace

	// Alt plus simple graphical key.
	{"\033a", K('a', ui.Alt)},
	{"\033[", K('[', ui.Alt)},

	// G3-style key.
	{"\033OA", K(ui.Up)},
	{"\033OH", K(ui.Home)},

	// G3-style key with leading Escape.

	// CSI-sequence key identified by the ending rune.
	{"\033[A", K(ui.Up)},
	{"\033[H", K(ui.Home)},
	// Modifiers.
	{"\033[1;1A", K(ui.Up)},
	{"\033[1;2A", K(ui.Up, ui.Shift)},
	{"\033[1;3A", K(ui.Up, ui.Alt)},
	{"\033[1;4A", K(ui.Up, ui.Shift, ui.Alt)},
	{"\033[1;5A", K(ui.Up, ui.Ctrl)},
	{"\033[1;6A", K(ui.Up, ui.Shift, ui.Ctrl)},
	{"\033[1;7A", K(ui.Up, ui.Alt, ui.Ctrl)},
	{"\033[1;8A", K(ui.Up, ui.Shift, ui.Alt, ui.Ctrl)},
	// The modifiers below should be for Meta, but we conflate Alt and Meta.
	{"\033[1;9A", K(ui.Up, ui.Alt)},
	{"\033[1;10A", K(ui.Up, ui.Shift, ui.Alt)},
	{"\033[1;11A", K(ui.Up, ui.Alt)},
	{"\033[1;12A", K(ui.Up, ui.Shift, ui.Alt)},
	{"\033[1;13A", K(ui.Up, ui.Alt, ui.Ctrl)},
	{"\033[1;14A", K(ui.Up, ui.Shift, ui.Alt, ui.Ctrl)},
	{"\033[1;15A", K(ui.Up, ui.Alt, ui.Ctrl)},
	{"\033[1;16A", K(ui.Up, ui.Shift, ui.Alt, ui.Ctrl)},

	// CSI-sequence key with one argument, always ending in '~'.
	{"\033[1~", K(ui.Home)},
	{"\033[11~", K(ui.F1)},

	// CSI-sequence key with three arguments and ending in '~'. The first
	// argument is always 27, the second identifies the modifier and the last
	// identifies the key.
	{"\033[27;4;63~", K(';', ui.Shift, ui.Alt)},
}

func TestReader_ReadKeys(t *testing.T) {
	r, w, reader := setupTest()
	defer r.Close()
	defer w.Close()
	reader.Start()
	defer reader.Close()
	defer reader.Stop()

	for _, test := range keyTests {
		w.WriteString(test.input)
		select {
		case event := <-reader.EventChan():
			if event != test.want {
				t.Errorf("Reader reads event %v, want %v", event, test.want)
			}
		case <-time.After(time.Second):
			t.Errorf("Reader timed out")
		}
	}
}

func TestReader_StopMakesUnderlyingFileAvailable(t *testing.T) {
	r, w, reader := setupTest()
	defer r.Close()
	defer w.Close()
	reader.Start()
	defer reader.Close()

	// tests that after calling Stop, the
	// Reader no longer attempts to read from the underlying file, so it is
	// available for use by others.
	reader.Stop()

	// Verify that the reader has indeed stopped: write something via w,
	// and get it back via r.
	written := "lorem ipsum"
	w.WriteString(written)
	var buf [32]byte
	nr, err := r.Read(buf[:])
	if err != nil {
		panic(err)
	}
	got := string(buf[:nr])
	if got != written {
		t.Errorf("got %q, want %q", got, written)
	}
}

func TestReader_StartAfterStopIndeedStarts(t *testing.T) {
	r, w, reader := setupTest()
	defer r.Close()
	defer w.Close()
	reader.Start()
	defer reader.Close()
	defer reader.Stop()

	for i := 0; i < 100; i++ {
		// Test that calling Start very shortly after Stop puts the Reader
		// in the correct started state. This test is for ensuring that the
		// operations do not have race conditions.
		reader.Stop()
		reader.Start()

		w.WriteString("a")
		select {
		case event := <-reader.EventChan():
			wantEvent := K('a')
			if event != wantEvent {
				t.Errorf("After Stop and Start, Reader reads %v, want %v", event, wantEvent)
			}
		case <-time.After(time.Second):
			t.Errorf("After Stop and Start, Reader timed out")
		}
	}
}

{'fraction_non_alphanumeric': 0.21018227529855438, 'fraction_numerical': 0.037209302325581395, 'mean_word_length': 3.424916573971079, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 2, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4123448', 'n_tokens_mistral': 3460, 'n_tokens_neox': 3151, 'n_words': 862}