<commit_before>package edit

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/elves/elvish/sys"
)

const (
	completionListingColMargin          int = 2
	navigationListingColMargin              = 1
	navigationListingColPadding             = 1
	navigationListingMinWidthForPadding     = 5
)

// cell is an indivisible unit on the screen. It is not necessarily 1 column
// wide.
type cell struct {
	rune
	width byte
	style string
}

// pos is the position within a buffer.
type pos struct {
	line, col int
}

var invalidPos = pos{-1, -1}

func lineWidth(cs []cell) int {
	w := 0
	for _, c := range cs {
		w += int(c.width)
	}
	return w
}

// buffer reflects a continuous range of lines on the terminal. The Unix
// terminal API provides only awkward ways of querying the terminal buffer, so
// we keep an internal reflection and do one-way synchronizations (buffer ->
// terminal, and not the other way around). This requires us to exactly match
// the terminal's idea of the width of characters (wcwidth) and where to
// insert soft carriage returns, so there could be bugs.
type buffer struct {
	width, col, indent int
	newlineWhenFull    bool
	cells              [][]cell // cells reflect len(cells) lines on the terminal.
	dot                pos      // dot is what the user perceives as the cursor.
}

func newBuffer(width int) *buffer {
	return &buffer{width: width, cells: [][]cell{make([]cell, 0, width)}}
}

func (b *buffer) appendCell(c cell) {
	n := len(b.cells)
	b.cells[n-1] = append(b.cells[n-1], c)
	b.col += int(c.width)
}

func (b *buffer) appendLine() {
	b.cells = append(b.cells, make([]cell, 0, b.width))
	b.col = 0
}

func (b *buffer) newline() {
	b.appendLine()

	if b.indent > 0 {
		for i := 0; i < b.indent; i++ {
			b.appendCell(cell{rune: ' ', width: 1})
		}
	}
}

func (b *buffer) extend(b2 *buffer) {
	if b2 != nil && b2.cells != nil {
		b.cells = append(b.cells, b2.cells...)
		b.col = b2.col
	}
}

func makeSpacing(n int) []cell {
	s := make([]cell, n)
	for i := 0; i < n; i++ {
		s[i].rune = ' '
		s[i].width = 1
	}
	return s
}

// extendHorizontal extends b horizontally, appending each line in b2 to b,
// preceeded by m margin. If b2 has more lines than b, the last len(b2) -
// len(b) lines are first filled with paddings of width w.
func (b *buffer) extendHorizontal(b2 *buffer, w, m int) {
	i := 0
	margin := makeSpacing(m)
	for ; i < len(b.cells) && i < len(b2.cells); i++ {
		if w0 := lineWidth(b.cells[i]); w0 < w {
			b.cells[i] = append(b.cells[i], makeSpacing(w-w0)...)
		}
		b.cells[i] = append(append(b.cells[i], margin...), b2.cells[i]...)
	}
	padding := makeSpacing(w + m)
	for ; i < len(b2.cells); i++ {
		row := make([]cell, 0, w+m+len(b2.cells[i]))
		row = append(append(row, padding...), b2.cells[i]...)
		b.cells = append(b.cells, row)
	}
}

// write appends a single rune to a buffer.
func (b *buffer) write(r rune, style string) {
	if r == '\n' {
		b.newline()
		return
	} else if !unicode.IsPrint(r) {
		// BUG(xiaq): buffer.write drops unprintable runes silently
		return
	}
	wd := WcWidth(r)
	c := cell{r, byte(wd), style}

	if b.col+wd > b.width {
		b.newline()
		b.appendCell(c)
	} else {
		b.appendCell(c)
		if b.col == b.width && b.newlineWhenFull {
			b.newline()
		}
	}
}

func (b *buffer) writes(s string, style string) {
	for _, r := range s {
		b.write(r, style)
	}
}

func (b *buffer) writePadding(w int, style string) {
	b.writes(strings.Repeat(" ", w), style)
}

func (b *buffer) line() int {
	return len(b.cells) - 1
}

func (b *buffer) cursor() pos {
	return pos{len(b.cells) - 1, b.col}
}

func (b *buffer) trimToLines(low, high int) {
	for i := 0; i < low; i++ {
		b.cells[i] = nil
	}
	for i := high; i < len(b.cells); i++ {
		b.cells[i] = nil
	}
	b.cells = b.cells[low:high]
	b.dot.line -= low
}

// writer renders the editor UI.
type writer struct {
	file   *os.File
	oldBuf *buffer
}

func newWriter(f *os.File) *writer {
	writer := &writer{file: f, oldBuf: &buffer{}}
	return writer
}

func (w *writer) resetOldBuf() {
	w.oldBuf = &buffer{}
}

// deltaPos calculates the escape sequence needed to move the cursor from one
// position to another. It use relative movements to move to the destination
// line and absolute movement to move to the destination column.
func deltaPos(from, to pos) []byte {
	buf := new(bytes.Buffer)
	if from.line < to.line {
		// move down
		fmt.Fprintf(buf, "\033[%dB", to.line-from.line)
	} else if from.line > to.line {
		// move up
		fmt.Fprintf(buf, "\033[%dA", from.line-to.line)
	}
	fmt.Fprintf(buf, "\033[%dG", to.col+1)
	return buf.Bytes()
}

func compareRows(r1, r2 []cell) (bool, int) {
	for i, c := range r1 {
		if i >= len(r2) || c != r2[i] {
			return false, i
		}
	}
	if len(r1) < len(r2) {
		return false, len(r1)
	}
	return true, 0
}

// commitBuffer updates the terminal display to reflect current buffer.
// TODO Instead of erasing w.oldBuf entirely and then draw buf, compute a
// delta between w.oldBuf and buf
func (w *writer) commitBuffer(bufNoti, buf *buffer, fullRefresh bool) error {
	if buf.width != w.oldBuf.width && w.oldBuf.cells != nil {
		// Width change, force full refresh
		w.oldBuf.cells = nil
		fullRefresh = true
	}

	bytesBuf := new(bytes.Buffer)

	// Rewind cursor
	if pLine := w.oldBuf.dot.line; pLine > 0 {
		fmt.Fprintf(bytesBuf, "\033[%dA", pLine)
	}
	bytesBuf.WriteString("\r")

	// style of last written cell.
	style := ""

	writeCells := func(cs []cell) {
		for _, c := range cs {
			if c.width > 0 && c.style != style {
				fmt.Fprintf(bytesBuf, "\033[m\033[%sm", c.style)
				style = c.style
			}
			bytesBuf.WriteString(string(c.rune))
		}
	}

	if bufNoti != nil {
		// Logger.Printf("going to write %d lines of notifications", len(bufNoti.cells))

		// Write notifications
		for _, line := range bufNoti.cells {
			writeCells(line)
			bytesBuf.WriteString("\033[K\n")
		}
		// XXX Hacky.
		if len(w.oldBuf.cells) > 0 {
			w.oldBuf.cells = w.oldBuf.cells[1:]
		}
	}

	// Logger.Printf("going to write %d lines, oldBuf had %d", len(buf.cells), len(w.oldBuf.cells))

	for i, line := range buf.cells {
		if i > 0 {
			bytesBuf.WriteString("\n")
		}
		var j int // First column where buf and oldBuf differ
		// No need to update current line
		if !fullRefresh && i < len(w.oldBuf.cells) {
			var eq bool
			if eq, j = compareRows(line, w.oldBuf.cells[i]); eq {
				continue
			}
		}
		// Move to the first differing column if necessary.
		firstCol := widthOfCells(line[:j])
		if firstCol != 0 {
			fmt.Fprintf(bytesBuf, "\033[%dG", firstCol+1)
		}
		// Erase the rest of the line if necessary.
		if i < len(w.oldBuf.cells) && j < len(w.oldBuf.cells[i]) {
			bytesBuf.WriteString("\033[K")
		}
		for _, c := range line[j:] {
			if c.width > 0 && c.style != style {
				fmt.Fprintf(bytesBuf, "\033[;%sm", c.style)
				style = c.style
			}
			bytesBuf.WriteString(string(c.rune))
		}
	}
	if len(w.oldBuf.cells) > len(buf.cells) || fullRefresh {
		// If the old buffer is higher, erase old content.
		// Note that we cannot simply write \033[J, because if the cursor is
		// just over the last column -- which is precisely the case if we have a
		// rprompt, \033[J will also erase the last column.
		bytesBuf.WriteString("\n\033[J\033[A")
	}
	if style != "" {
		bytesBuf.WriteString("\033[m")
	}
	cursor := buf.cursor()
	bytesBuf.Write(deltaPos(cursor, buf.dot))

	Logger.Printf("going to write %q", bytesBuf.String())

	_, err := w.file.Write(bytesBuf.Bytes())
	if err != nil {
		return err
	}

	w.oldBuf = buf
	return nil
}

func widthOfCells(cells []cell) int {
	w := 0
	for _, c := range cells {
		w += int(c.width)
	}<commit_msg>edit: When doing a refresh, erase just once in the beginning.<commit_after>package edit

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/elves/elvish/sys"
)

const (
	completionListingColMargin          int = 2
	navigationListingColMargin              = 1
	navigationListingColPadding             = 1
	navigationListingMinWidthForPadding     = 5
)

// cell is an indivisible unit on the screen. It is not necessarily 1 column
// wide.
type cell struct {
	rune
	width byte
	style string
}

// pos is the position within a buffer.
type pos struct {
	line, col int
}

var invalidPos = pos{-1, -1}

func lineWidth(cs []cell) int {
	w := 0
	for _, c := range cs {
		w += int(c.width)
	}
	return w
}

// buffer reflects a continuous range of lines on the terminal. The Unix
// terminal API provides only awkward ways of querying the terminal buffer, so
// we keep an internal reflection and do one-way synchronizations (buffer ->
// terminal, and not the other way around). This requires us to exactly match
// the terminal's idea of the width of characters (wcwidth) and where to
// insert soft carriage returns, so there could be bugs.
type buffer struct {
	width, col, indent int
	newlineWhenFull    bool
	cells              [][]cell // cells reflect len(cells) lines on the terminal.
	dot                pos      // dot is what the user perceives as the cursor.
}

func newBuffer(width int) *buffer {
	return &buffer{width: width, cells: [][]cell{make([]cell, 0, width)}}
}

func (b *buffer) appendCell(c cell) {
	n := len(b.cells)
	b.cells[n-1] = append(b.cells[n-1], c)
	b.col += int(c.width)
}

func (b *buffer) appendLine() {
	b.cells = append(b.cells, make([]cell, 0, b.width))
	b.col = 0
}

func (b *buffer) newline() {
	b.appendLine()

	if b.indent > 0 {
		for i := 0; i < b.indent; i++ {
			b.appendCell(cell{rune: ' ', width: 1})
		}
	}
}

func (b *buffer) extend(b2 *buffer) {
	if b2 != nil && b2.cells != nil {
		b.cells = append(b.cells, b2.cells...)
		b.col = b2.col
	}
}

func makeSpacing(n int) []cell {
	s := make([]cell, n)
	for i := 0; i < n; i++ {
		s[i].rune = ' '
		s[i].width = 1
	}
	return s
}

// extendHorizontal extends b horizontally, appending each line in b2 to b,
// preceeded by m margin. If b2 has more lines than b, the last len(b2) -
// len(b) lines are first filled with paddings of width w.
func (b *buffer) extendHorizontal(b2 *buffer, w, m int) {
	i := 0
	margin := makeSpacing(m)
	for ; i < len(b.cells) && i < len(b2.cells); i++ {
		if w0 := lineWidth(b.cells[i]); w0 < w {
			b.cells[i] = append(b.cells[i], makeSpacing(w-w0)...)
		}
		b.cells[i] = append(append(b.cells[i], margin...), b2.cells[i]...)
	}
	padding := makeSpacing(w + m)
	for ; i < len(b2.cells); i++ {
		row := make([]cell, 0, w+m+len(b2.cells[i]))
		row = append(append(row, padding...), b2.cells[i]...)
		b.cells = append(b.cells, row)
	}
}

// write appends a single rune to a buffer.
func (b *buffer) write(r rune, style string) {
	if r == '\n' {
		b.newline()
		return
	} else if !unicode.IsPrint(r) {
		// BUG(xiaq): buffer.write drops unprintable runes silently
		return
	}
	wd := WcWidth(r)
	c := cell{r, byte(wd), style}

	if b.col+wd > b.width {
		b.newline()
		b.appendCell(c)
	} else {
		b.appendCell(c)
		if b.col == b.width && b.newlineWhenFull {
			b.newline()
		}
	}
}

func (b *buffer) writes(s string, style string) {
	for _, r := range s {
		b.write(r, style)
	}
}

func (b *buffer) writePadding(w int, style string) {
	b.writes(strings.Repeat(" ", w), style)
}

func (b *buffer) line() int {
	return len(b.cells) - 1
}

func (b *buffer) cursor() pos {
	return pos{len(b.cells) - 1, b.col}
}

func (b *buffer) trimToLines(low, high int) {
	for i := 0; i < low; i++ {
		b.cells[i] = nil
	}
	for i := high; i < len(b.cells); i++ {
		b.cells[i] = nil
	}
	b.cells = b.cells[low:high]
	b.dot.line -= low
}

// writer renders the editor UI.
type writer struct {
	file   *os.File
	oldBuf *buffer
}

func newWriter(f *os.File) *writer {
	writer := &writer{file: f, oldBuf: &buffer{}}
	return writer
}

func (w *writer) resetOldBuf() {
	w.oldBuf = &buffer{}
}

// deltaPos calculates the escape sequence needed to move the cursor from one
// position to another. It use relative movements to move to the destination
// line and absolute movement to move to the destination column.
func deltaPos(from, to pos) []byte {
	buf := new(bytes.Buffer)
	if from.line < to.line {
		// move down
		fmt.Fprintf(buf, "\033[%dB", to.line-from.line)
	} else if from.line > to.line {
		// move up
		fmt.Fprintf(buf, "\033[%dA", from.line-to.line)
	}
	fmt.Fprintf(buf, "\033[%dG", to.col+1)
	return buf.Bytes()
}

func compareRows(r1, r2 []cell) (bool, int) {
	for i, c := range r1 {
		if i >= len(r2) || c != r2[i] {
			return false, i
		}
	}
	if len(r1) < len(r2) {
		return false, len(r1)
	}
	return true, 0
}

// commitBuffer updates the terminal display to reflect current buffer.
// TODO Instead of erasing w.oldBuf entirely and then draw buf, compute a
// delta between w.oldBuf and buf
func (w *writer) commitBuffer(bufNoti, buf *buffer, fullRefresh bool) error {
	if buf.width != w.oldBuf.width && w.oldBuf.cells != nil {
		// Width change, force full refresh
		w.oldBuf.cells = nil
		fullRefresh = true
	}

	bytesBuf := new(bytes.Buffer)

	// Rewind cursor
	if pLine := w.oldBuf.dot.line; pLine > 0 {
		fmt.Fprintf(bytesBuf, "\033[%dA", pLine)
	}
	bytesBuf.WriteString("\r")

	if fullRefresh {
		// Do an erase.
		bytesBuf.WriteString("\033[J")
	}

	// style of last written cell.
	style := ""

	writeCells := func(cs []cell) {
		for _, c := range cs {
			if c.width > 0 && c.style != style {
				fmt.Fprintf(bytesBuf, "\033[m\033[%sm", c.style)
				style = c.style
			}
			bytesBuf.WriteString(string(c.rune))
		}
	}

	if bufNoti != nil {
		// Logger.Printf("going to write %d lines of notifications", len(bufNoti.cells))

		// Write notifications
		for _, line := range bufNoti.cells {
			writeCells(line)
			bytesBuf.WriteString("\033[K\n")
		}
		// XXX Hacky.
		if len(w.oldBuf.cells) > 0 {
			w.oldBuf.cells = w.oldBuf.cells[1:]
		}
	}

	// Logger.Printf("going to write %d lines, oldBuf had %d", len(buf.cells), len(w.oldBuf.cells))

	for i, line := range buf.cells {
		if i > 0 {
			bytesBuf.WriteString("\n")
		}
		var j int // First column where buf and oldBuf differ
		// No need to update current line
		if !fullRefresh && i < len(w.oldBuf.cells) {
			var eq bool
			if eq, j = compareRows(line, w.oldBuf.cells[i]); eq {
				continue
			}
		}
		// Move to the first differing column if necessary.
		firstCol := widthOfCells(line[:j])
		if firstCol != 0 {
			fmt.Fprintf(bytesBuf, "\033[%dG", firstCol+1)
		}
		// Erase the rest of the line if necessary.
		if !fullRefresh && i < len(w.oldBuf.cells) && j < len(w.oldBuf.cells[i]) {
			bytesBuf.WriteString("\033[K")
		}
		for _, c := range line[j:] {
			if c.width > 0 && c.style != style {
				fmt.Fprintf(bytesBuf, "\033[;%sm", c.style)
				style = c.style
			}
			bytesBuf.WriteString(string(c.rune))
		}
	}
	if len(w.oldBuf.cells) > len(buf.cells) && !fullRefresh {
		// If the old buffer is higher, erase old content.
		// Note that we cannot simply write \033[J, because if the cursor is
		// just over the last column -- which is precisely the case if we have a
		// rprompt, \033[J will also erase the last column.
		bytesBuf.WriteString("\n\033[J\033[A")
	}
	if style != "" {
		bytesBuf.WriteString("\033[m")
	}
	cursor := buf.cursor()
	bytesBuf.Write(deltaPos(cursor, buf.dot))

	Logger.Printf("going to write %q", bytesBuf.String())

	_, err := w.file.Write(bytesBuf.Bytes())
	if err != nil {
		return err
	}

	w.oldBuf = buf
	return nil
}

func widthOfCells(cells []cell) int {
	w := 0
	for _, c := range cells {
		w += int(c.width)
	}
{'fraction_non_alphanumeric': 0.15679510544129133, 'fraction_numerical': 0.01386357719343921, 'mean_word_length': 2.983666061705989, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, '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': '294429', 'n_tokens_mistral': 6287, 'n_tokens_neox': 5744, 'n_words': 1741}