<commit_before>package edit

import (
	"container/list"
	"strings"
	"unicode/utf8"

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

type renderer interface {
	render(b *buffer)
}

func render(r renderer, width int) *buffer {
	if r == nil {
		return nil
	}
	b := newBuffer(width)
	r.render(b)
	return b
}

type modeLineRenderer struct {
	title  string
	filter string
}

func (ml modeLineRenderer) render(b *buffer) {
	b.writes(ml.title, styleForMode.String())
	b.writes(" ", "")
	b.writes(ml.filter, styleForFilter.String())
	b.dot = b.cursor()
}

type modeLineWithScrollBarRenderer struct {
	modeLineRenderer
	n, low, high int
}

func (ml modeLineWithScrollBarRenderer) render(b *buffer) {
	ml.modeLineRenderer.render(b)

	scrollbarWidth := b.width - lineWidth(b.cells[len(b.cells)-1]) - 2
	if scrollbarWidth >= 3 {
		b.writes(" ", "")
		writeHorizontalScrollbar(b, ml.n, ml.low, ml.high, scrollbarWidth)
	}
}

type placeholderRenderer string

func (lp placeholderRenderer) render(b *buffer) {
	b.writes(util.TrimWcwidth(string(lp), b.width), "")
}

type listingRenderer struct {
	// A List of styled items.
	list.List
}

func (ls listingRenderer) render(b *buffer) {
	for p := ls.Front(); p != nil; p = p.Next() {
		line := p.Value.(styled)
		if p != ls.Front() {
			b.newline()
		}
		b.writes(util.ForceWcwidth(line.text, b.width), line.styles.String())
	}
}

type listingWithScrollBarRenderer struct {
	listingRenderer
	n, low, high, height int
}

func (ls listingWithScrollBarRenderer) render(b *buffer) {
	b1 := render(ls.listingRenderer, b.width-1)
	b.extendHorizontal(b1, 0)

	scrollbar := renderScrollbar(ls.n, ls.low, ls.high, ls.height)
	b.extendHorizontal(scrollbar, b.width-1)
}

type navRenderer struct {
	maxHeight                      int
	fwParent, fwCurrent, fwPreview int
	parent, current, preview       renderer
}

func makeNavRenderer(h int, w1, w2, w3 int, r1, r2, r3 renderer) renderer {
	return &navRenderer{h, w1, w2, w3, r1, r2, r3}
}

func (nr *navRenderer) render(b *buffer) {
	margin := navigationListingColMargin

	w := b.width - margin*2
	ws := distributeWidths(w,
		[]float64{parentColumnWeight, currentColumnWeight, previewColumnWeight},
		[]int{nr.fwParent, nr.fwCurrent, nr.fwPreview},
	)
	wParent, wCurrent, wPreview := ws[0], ws[1], ws[2]

	bParent := render(nr.parent, wParent)
	b.extendHorizontal(bParent, 0)

	bCurrent := render(nr.current, wCurrent)
	b.extendHorizontal(bCurrent, wParent+margin)

	if wPreview > 0 {
		bPreview := render(nr.preview, wPreview)
		b.extendHorizontal(bPreview, wParent+wCurrent+2*margin)
	}
}

// linesRenderer renders lines with a uniform style.
type linesRenderer struct {
	lines []string
	style string
}

func (nr linesRenderer) render(b *buffer) {
	b.writes(strings.Join(nr.lines, "\n"), "")
}

// cmdlineRenderer renders the command line, including the prompt, the user's
// input and the rprompt.
type cmdlineRenderer struct {
	prompt  []*styled
	tokens  []Token
	dot     int
	rprompt []*styled

	hasComp   bool
	compBegin int
	compEnd   int
	compText  string

	hasHist   bool
	histBegin int
	histText  string
}

func newCmdlineRenderer(p []*styled, t []Token, d int, rp []*styled) *cmdlineRenderer {
	return &cmdlineRenderer{prompt: p, tokens: t, dot: d, rprompt: rp}
}

func (clr *cmdlineRenderer) setComp(b, e int, t string) {
	clr.hasComp = true
	clr.compBegin, clr.compEnd, clr.compText = b, e, t
}

func (clr *cmdlineRenderer) setHist(b int, t string) {
	clr.hasHist = true
	clr.histBegin, clr.histText = b, t
}

func (clr *cmdlineRenderer) render(b *buffer) {
	b.newlineWhenFull = true

	b.writeStyleds(clr.prompt)

	if b.line() == 0 && b.col*2 < b.width {
		b.indent = b.col
	}

	// i keeps track of number of bytes written.
	i := 0

	// nowAt is called at every rune boundary.
	nowAt := func(i int) {
		if clr.hasComp && i == clr.compBegin {
			b.writes(clr.compText, styleForCompleted.String())
		}
		if i == clr.dot {
			b.dot = b.cursor()
		}
	}
	nowAt(0)
tokens:
	for _, token := range clr.tokens {
		for _, r := range token.Text {
			if clr.hasComp && clr.compBegin <= i && i < clr.compEnd {
				// Do nothing. This part is replaced by the completion candidate.
			} else {
				b.write(r, joinStyles(styleForType[token.Type], token.MoreStyle).String())
			}
			i += utf8.RuneLen(r)

			nowAt(i)
			if clr.hasHist && i == clr.histBegin {
				// Put the rest of current history, position the cursor at the
				// end of the line, and finish writing
				b.writes(clr.histText, styleForCompletedHistory.String())
				b.dot = b.cursor()
				break tokens
			}
		}
	}

	// Write rprompt<commit_msg>Fix rendering of history when prefix is empty.<commit_after>package edit

import (
	"container/list"
	"strings"
	"unicode/utf8"

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

type renderer interface {
	render(b *buffer)
}

func render(r renderer, width int) *buffer {
	if r == nil {
		return nil
	}
	b := newBuffer(width)
	r.render(b)
	return b
}

type modeLineRenderer struct {
	title  string
	filter string
}

func (ml modeLineRenderer) render(b *buffer) {
	b.writes(ml.title, styleForMode.String())
	b.writes(" ", "")
	b.writes(ml.filter, styleForFilter.String())
	b.dot = b.cursor()
}

type modeLineWithScrollBarRenderer struct {
	modeLineRenderer
	n, low, high int
}

func (ml modeLineWithScrollBarRenderer) render(b *buffer) {
	ml.modeLineRenderer.render(b)

	scrollbarWidth := b.width - lineWidth(b.cells[len(b.cells)-1]) - 2
	if scrollbarWidth >= 3 {
		b.writes(" ", "")
		writeHorizontalScrollbar(b, ml.n, ml.low, ml.high, scrollbarWidth)
	}
}

type placeholderRenderer string

func (lp placeholderRenderer) render(b *buffer) {
	b.writes(util.TrimWcwidth(string(lp), b.width), "")
}

type listingRenderer struct {
	// A List of styled items.
	list.List
}

func (ls listingRenderer) render(b *buffer) {
	for p := ls.Front(); p != nil; p = p.Next() {
		line := p.Value.(styled)
		if p != ls.Front() {
			b.newline()
		}
		b.writes(util.ForceWcwidth(line.text, b.width), line.styles.String())
	}
}

type listingWithScrollBarRenderer struct {
	listingRenderer
	n, low, high, height int
}

func (ls listingWithScrollBarRenderer) render(b *buffer) {
	b1 := render(ls.listingRenderer, b.width-1)
	b.extendHorizontal(b1, 0)

	scrollbar := renderScrollbar(ls.n, ls.low, ls.high, ls.height)
	b.extendHorizontal(scrollbar, b.width-1)
}

type navRenderer struct {
	maxHeight                      int
	fwParent, fwCurrent, fwPreview int
	parent, current, preview       renderer
}

func makeNavRenderer(h int, w1, w2, w3 int, r1, r2, r3 renderer) renderer {
	return &navRenderer{h, w1, w2, w3, r1, r2, r3}
}

func (nr *navRenderer) render(b *buffer) {
	margin := navigationListingColMargin

	w := b.width - margin*2
	ws := distributeWidths(w,
		[]float64{parentColumnWeight, currentColumnWeight, previewColumnWeight},
		[]int{nr.fwParent, nr.fwCurrent, nr.fwPreview},
	)
	wParent, wCurrent, wPreview := ws[0], ws[1], ws[2]

	bParent := render(nr.parent, wParent)
	b.extendHorizontal(bParent, 0)

	bCurrent := render(nr.current, wCurrent)
	b.extendHorizontal(bCurrent, wParent+margin)

	if wPreview > 0 {
		bPreview := render(nr.preview, wPreview)
		b.extendHorizontal(bPreview, wParent+wCurrent+2*margin)
	}
}

// linesRenderer renders lines with a uniform style.
type linesRenderer struct {
	lines []string
	style string
}

func (nr linesRenderer) render(b *buffer) {
	b.writes(strings.Join(nr.lines, "\n"), "")
}

// cmdlineRenderer renders the command line, including the prompt, the user's
// input and the rprompt.
type cmdlineRenderer struct {
	prompt  []*styled
	tokens  []Token
	dot     int
	rprompt []*styled

	hasComp   bool
	compBegin int
	compEnd   int
	compText  string

	hasHist   bool
	histBegin int
	histText  string
}

func newCmdlineRenderer(p []*styled, t []Token, d int, rp []*styled) *cmdlineRenderer {
	return &cmdlineRenderer{prompt: p, tokens: t, dot: d, rprompt: rp}
}

func (clr *cmdlineRenderer) setComp(b, e int, t string) {
	clr.hasComp = true
	clr.compBegin, clr.compEnd, clr.compText = b, e, t
}

func (clr *cmdlineRenderer) setHist(b int, t string) {
	clr.hasHist = true
	clr.histBegin, clr.histText = b, t
}

func (clr *cmdlineRenderer) render(b *buffer) {
	b.newlineWhenFull = true

	b.writeStyleds(clr.prompt)

	if b.line() == 0 && b.col*2 < b.width {
		b.indent = b.col
	}

	// i keeps track of number of bytes written.
	i := 0

	// nowAt is called at every rune boundary.
	nowAt := func(i int) {
		if clr.hasComp && i == clr.compBegin {
			b.writes(clr.compText, styleForCompleted.String())
		}
		if i == clr.dot {
			b.dot = b.cursor()
		}
	}
	nowAt(0)
tokens:
	for _, token := range clr.tokens {
		for _, r := range token.Text {
			if clr.hasComp && clr.compBegin <= i && i < clr.compEnd {
				// Do nothing. This part is replaced by the completion candidate.
			} else {
				b.write(r, joinStyles(styleForType[token.Type], token.MoreStyle).String())
			}
			i += utf8.RuneLen(r)

			nowAt(i)
			if clr.hasHist && i == clr.histBegin {
				break tokens
			}
		}
	}

	if clr.hasHist {
		// Put the rest of current history and position the cursor at the
		// end of the line.
		b.writes(clr.histText, styleForCompletedHistory.String())
		b.dot = b.cursor()
	}

	// Write rprompt
{'fraction_non_alphanumeric': 0.13496999454446262, 'fraction_numerical': 0.007637752318603383, 'mean_word_length': 3.754149377593361, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4351453', 'n_tokens_mistral': 3578, 'n_tokens_neox': 3370, 'n_words': 870}