2013-09-19 18:36:47 +08:00
|
|
|
package edit
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2014-01-16 09:24:14 +08:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2013-12-31 11:31:59 +08:00
|
|
|
"strings"
|
2013-10-03 21:29:46 +08:00
|
|
|
"unicode"
|
2013-10-03 21:52:46 +08:00
|
|
|
"unicode/utf8"
|
2014-02-10 11:33:53 +08:00
|
|
|
|
|
|
|
"github.com/xiaq/elvish/edit/tty"
|
|
|
|
"github.com/xiaq/elvish/util"
|
2013-09-19 18:36:47 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// cell is an indivisible unit on the screen. It is not necessarily 1 column
|
|
|
|
// wide.
|
|
|
|
type cell struct {
|
|
|
|
rune
|
|
|
|
width byte
|
2014-01-16 09:24:14 +08:00
|
|
|
attr string
|
2013-09-19 18:36:47 +08:00
|
|
|
}
|
|
|
|
|
2013-09-20 09:28:32 +08:00
|
|
|
// pos is the position within a buffer.
|
|
|
|
type pos struct {
|
|
|
|
line, col int
|
|
|
|
}
|
|
|
|
|
2014-01-08 16:26:03 +08:00
|
|
|
// 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.
|
2013-09-20 00:26:19 +08:00
|
|
|
type buffer struct {
|
2014-01-09 17:30:17 +08:00
|
|
|
width, col, indent int
|
2014-01-16 09:24:14 +08:00
|
|
|
newlineWhenFull bool
|
|
|
|
cells [][]cell // cells reflect len(cells) lines on the terminal.
|
|
|
|
dot pos // dot is what the user perceives as the cursor.
|
2013-09-20 00:26:19 +08:00
|
|
|
}
|
|
|
|
|
2014-01-09 17:30:17 +08:00
|
|
|
func newBuffer(width int) *buffer {
|
|
|
|
return &buffer{width: width, cells: [][]cell{make([]cell, 0, width)}}
|
2013-09-20 09:28:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *buffer) appendCell(c cell) {
|
|
|
|
n := len(b.cells)
|
|
|
|
b.cells[n-1] = append(b.cells[n-1], c)
|
2014-01-09 17:30:17 +08:00
|
|
|
b.col += int(c.width)
|
2013-09-20 09:28:32 +08:00
|
|
|
}
|
|
|
|
|
2014-01-09 17:30:17 +08:00
|
|
|
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})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-09 20:16:27 +08:00
|
|
|
func (b *buffer) extend(b2 *buffer) {
|
|
|
|
if b2 != nil && b2.cells != nil {
|
|
|
|
b.cells = append(b.cells, b2.cells...)
|
|
|
|
b.col = b2.col
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-09 17:30:17 +08:00
|
|
|
// write appends a single rune to a buffer.
|
|
|
|
func (b *buffer) write(r rune, attr string) {
|
|
|
|
if r == '\n' {
|
|
|
|
b.newline()
|
|
|
|
return
|
|
|
|
} else if !unicode.IsPrint(r) {
|
2014-02-08 19:22:25 +08:00
|
|
|
// BUG(xiaq): buffer.write drops unprintable runes silently
|
2014-01-09 17:30:17 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
wd := wcwidth(r)
|
|
|
|
c := cell{r, byte(wd), attr}
|
|
|
|
|
2014-01-16 09:24:14 +08:00
|
|
|
if b.col+wd > b.width {
|
2014-01-09 17:30:17 +08:00
|
|
|
b.newline()
|
|
|
|
b.appendCell(c)
|
|
|
|
} else {
|
|
|
|
b.appendCell(c)
|
2014-01-10 12:59:21 +08:00
|
|
|
if b.col == b.width && b.newlineWhenFull {
|
|
|
|
b.newline()
|
|
|
|
}
|
2014-01-09 17:30:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *buffer) writes(s string, attr string) {
|
|
|
|
for _, r := range s {
|
|
|
|
b.write(r, attr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-09 20:23:54 +08:00
|
|
|
func (b *buffer) writePadding(w int, attr string) {
|
|
|
|
b.writes(strings.Repeat(" ", w), attr)
|
|
|
|
}
|
|
|
|
|
2014-01-09 17:30:17 +08:00
|
|
|
func (b *buffer) line() int {
|
|
|
|
return len(b.cells) - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *buffer) cursor() pos {
|
|
|
|
return pos{len(b.cells) - 1, b.col}
|
2013-09-20 00:26:19 +08:00
|
|
|
}
|
2013-09-19 18:36:47 +08:00
|
|
|
|
2014-01-10 12:51:20 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-09-19 18:36:47 +08:00
|
|
|
// writer is the part of an Editor responsible for keeping the status of and
|
|
|
|
// updating the screen.
|
|
|
|
type writer struct {
|
2014-01-16 09:24:14 +08:00
|
|
|
file *os.File
|
2014-01-09 20:16:27 +08:00
|
|
|
oldBuf *buffer
|
2013-09-19 18:36:47 +08:00
|
|
|
}
|
|
|
|
|
2013-12-26 20:39:59 +08:00
|
|
|
func newWriter(f *os.File) *writer {
|
|
|
|
writer := &writer{file: f, oldBuf: newBuffer(0)}
|
2013-09-19 18:36:47 +08:00
|
|
|
return writer
|
|
|
|
}
|
|
|
|
|
2013-10-03 21:44:37 +08:00
|
|
|
// deltaPos calculates the escape sequence needed to move the cursor from one
|
|
|
|
// position to another.
|
2013-09-20 09:28:32 +08:00
|
|
|
func deltaPos(from, to pos) []byte {
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
if from.line < to.line {
|
|
|
|
// move down
|
2014-02-08 22:34:45 +08:00
|
|
|
fmt.Fprintf(buf, "\033[%dB", to.line-from.line)
|
2013-09-20 09:28:32 +08:00
|
|
|
} else if from.line > to.line {
|
|
|
|
// move up
|
2014-02-08 22:34:45 +08:00
|
|
|
fmt.Fprintf(buf, "\033[%dA", from.line-to.line)
|
2013-09-20 09:28:32 +08:00
|
|
|
}
|
2014-02-08 22:36:05 +08:00
|
|
|
fmt.Fprintf(buf, "\033[%dG", to.col+1)
|
2013-09-20 09:28:32 +08:00
|
|
|
return buf.Bytes()
|
|
|
|
}
|
|
|
|
|
2013-10-03 21:44:37 +08:00
|
|
|
// commitBuffer updates the terminal display to reflect current buffer.
|
2014-01-09 20:16:27 +08:00
|
|
|
// TODO Instead of erasing w.oldBuf entirely and then draw buf, compute a
|
|
|
|
// delta between w.oldBuf and buf
|
|
|
|
func (w *writer) commitBuffer(buf *buffer) error {
|
2013-09-19 18:36:47 +08:00
|
|
|
bytesBuf := new(bytes.Buffer)
|
|
|
|
|
2013-10-03 21:57:44 +08:00
|
|
|
pLine := w.oldBuf.dot.line
|
2013-09-20 09:28:32 +08:00
|
|
|
if pLine > 0 {
|
|
|
|
fmt.Fprintf(bytesBuf, "\033[%dA", pLine)
|
2013-09-19 18:36:47 +08:00
|
|
|
}
|
|
|
|
bytesBuf.WriteString("\r\033[J")
|
|
|
|
|
|
|
|
attr := ""
|
2014-01-09 20:18:58 +08:00
|
|
|
for i, line := range buf.cells {
|
|
|
|
if i > 0 {
|
|
|
|
bytesBuf.WriteString("\n")
|
|
|
|
}
|
2013-09-19 18:36:47 +08:00
|
|
|
for _, c := range line {
|
|
|
|
if c.width > 0 && c.attr != attr {
|
2013-10-28 10:37:26 +08:00
|
|
|
fmt.Fprintf(bytesBuf, "\033[m\033[%sm", c.attr)
|
2013-09-19 18:36:47 +08:00
|
|
|
attr = c.attr
|
|
|
|
}
|
|
|
|
bytesBuf.WriteString(string(c.rune))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if attr != "" {
|
|
|
|
bytesBuf.WriteString("\033[m")
|
|
|
|
}
|
2014-01-10 12:59:21 +08:00
|
|
|
cursor := buf.cursor()
|
|
|
|
if cursor.col == buf.width {
|
|
|
|
cursor.col--
|
|
|
|
}
|
|
|
|
bytesBuf.Write(deltaPos(cursor, buf.dot))
|
2013-09-20 09:28:32 +08:00
|
|
|
|
2013-09-21 10:32:53 +08:00
|
|
|
_, err := w.file.Write(bytesBuf.Bytes())
|
2013-09-19 18:36:47 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-01-09 20:16:27 +08:00
|
|
|
w.oldBuf = buf
|
2013-09-19 18:36:47 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-01-16 09:24:14 +08:00
|
|
|
func lines(bufs ...*buffer) (l int) {
|
2014-01-10 12:51:20 +08:00
|
|
|
for _, buf := range bufs {
|
|
|
|
if buf != nil {
|
|
|
|
l += len(buf.cells)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-03-06 17:27:03 +08:00
|
|
|
// findWindow finds a window of lines around the selected line in a total
|
|
|
|
// number of height lines, that is at most max lines.
|
|
|
|
func findWindow(height, selected, max int) (low, high int) {
|
|
|
|
if height > max {
|
|
|
|
low = selected - max/2
|
|
|
|
high = low + max
|
|
|
|
switch {
|
|
|
|
case low < 0:
|
|
|
|
// Near top of the list, move the window down
|
|
|
|
low = 0
|
|
|
|
high = low + max
|
|
|
|
case high > height:
|
|
|
|
// Near bottom of the list, move the window down
|
|
|
|
high = height
|
|
|
|
low = high - max
|
|
|
|
}
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
return 0, height
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func trimToWindow(s []string, selected, max int) ([]string, int) {
|
|
|
|
low, high := findWindow(len(s), selected, max)
|
|
|
|
return s[low:high], low
|
|
|
|
}
|
|
|
|
|
2013-10-03 22:10:57 +08:00
|
|
|
// refresh redraws the line editor. The dot is passed as an index into text;
|
|
|
|
// the corresponding position will be calculated.
|
2014-01-08 16:11:38 +08:00
|
|
|
func (w *writer) refresh(bs *editorState) error {
|
2014-01-10 12:51:20 +08:00
|
|
|
winsize := tty.GetWinsize(int(w.file.Fd()))
|
|
|
|
width, height := int(winsize.Col), int(winsize.Row)
|
2014-01-09 20:16:27 +08:00
|
|
|
|
2014-03-06 01:09:57 +08:00
|
|
|
var bufLine, bufMode, bufTips, bufListing, buf *buffer
|
2014-01-09 20:16:27 +08:00
|
|
|
// bufLine
|
|
|
|
b := newBuffer(width)
|
|
|
|
bufLine = b
|
2013-09-19 18:36:47 +08:00
|
|
|
|
2014-01-10 12:59:21 +08:00
|
|
|
b.newlineWhenFull = true
|
|
|
|
|
2014-01-09 17:30:17 +08:00
|
|
|
b.writes(bs.prompt, attrForPrompt)
|
2013-09-19 18:36:47 +08:00
|
|
|
|
2014-01-16 09:24:14 +08:00
|
|
|
if b.line() == 0 && b.col*2 < b.width {
|
2014-01-09 17:30:17 +08:00
|
|
|
b.indent = b.col
|
2013-09-19 18:36:47 +08:00
|
|
|
}
|
|
|
|
|
2013-12-27 09:13:56 +08:00
|
|
|
// i keeps track of number of bytes written.
|
2013-10-03 21:37:07 +08:00
|
|
|
i := 0
|
2014-01-03 14:21:25 +08:00
|
|
|
if bs.dot == 0 {
|
2014-01-09 17:30:17 +08:00
|
|
|
b.dot = b.cursor()
|
2013-10-03 21:37:07 +08:00
|
|
|
}
|
2013-11-19 20:28:45 +08:00
|
|
|
|
2014-01-03 14:21:25 +08:00
|
|
|
comp := bs.completion
|
2014-01-03 14:21:54 +08:00
|
|
|
var suppress = false
|
2014-02-09 19:53:21 +08:00
|
|
|
|
|
|
|
tokens:
|
2014-01-03 14:21:25 +08:00
|
|
|
for _, token := range bs.tokens {
|
2013-09-19 18:36:47 +08:00
|
|
|
for _, r := range token.Val {
|
2013-12-30 16:18:24 +08:00
|
|
|
if suppress && i < comp.end {
|
2013-12-27 09:37:50 +08:00
|
|
|
// Silence the part that is being completed
|
|
|
|
} else {
|
2014-01-09 17:30:17 +08:00
|
|
|
b.write(r, attrForType[token.Typ])
|
2013-12-27 09:37:50 +08:00
|
|
|
}
|
2013-10-03 21:52:46 +08:00
|
|
|
i += utf8.RuneLen(r)
|
2013-12-30 16:18:24 +08:00
|
|
|
if comp != nil && comp.current != -1 && i == comp.start {
|
|
|
|
// Put the current candidate and instruct text up to comp.end
|
|
|
|
// to be suppressed. The cursor should be placed correctly
|
|
|
|
// (i.e. right after the candidate)
|
|
|
|
for _, part := range comp.candidates[comp.current].parts {
|
2014-01-08 16:11:12 +08:00
|
|
|
attr := attrForType[comp.typ]
|
2013-12-30 16:18:24 +08:00
|
|
|
if part.completed {
|
2014-01-08 16:11:12 +08:00
|
|
|
attr += attrForCompleted
|
2013-12-30 16:18:24 +08:00
|
|
|
}
|
2014-01-09 17:30:17 +08:00
|
|
|
b.writes(part.text, attr)
|
2013-12-30 16:18:24 +08:00
|
|
|
}
|
2014-01-03 14:21:54 +08:00
|
|
|
suppress = true
|
2013-12-30 16:18:24 +08:00
|
|
|
}
|
2014-02-10 11:33:53 +08:00
|
|
|
if bs.mode == modeHistory && i == len(bs.history.prefix) {
|
2014-02-09 19:53:21 +08:00
|
|
|
break tokens
|
|
|
|
}
|
2014-01-03 14:21:25 +08:00
|
|
|
if bs.dot == i {
|
2014-01-09 17:30:17 +08:00
|
|
|
b.dot = b.cursor()
|
2013-10-03 21:37:07 +08:00
|
|
|
}
|
2013-09-19 18:36:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-10 11:33:53 +08:00
|
|
|
if bs.mode == modeHistory {
|
2014-02-09 19:59:06 +08:00
|
|
|
// Put the rest of current history, position the cursor at the
|
|
|
|
// end of the line, and finish writing
|
|
|
|
h := bs.history
|
|
|
|
b.writes(h.items[h.current][len(h.prefix):], attrForCompletedHistory)
|
|
|
|
b.dot = b.cursor()
|
|
|
|
}
|
|
|
|
|
2014-01-04 22:45:35 +08:00
|
|
|
// Write rprompt
|
2014-01-10 12:59:21 +08:00
|
|
|
padding := b.width - b.col - wcwidths(bs.rprompt)
|
2014-01-04 22:45:35 +08:00
|
|
|
if padding >= 1 {
|
2014-01-10 12:59:21 +08:00
|
|
|
b.newlineWhenFull = false
|
2014-01-09 20:23:54 +08:00
|
|
|
b.writePadding(padding, "")
|
2014-01-09 17:30:17 +08:00
|
|
|
b.writes(bs.rprompt, attrForRprompt)
|
2014-01-04 22:45:35 +08:00
|
|
|
}
|
|
|
|
|
2014-01-09 20:16:27 +08:00
|
|
|
// bufMode
|
2014-01-31 20:01:07 +08:00
|
|
|
if bs.mode != modeInsert {
|
2014-01-09 20:16:27 +08:00
|
|
|
b := newBuffer(width)
|
|
|
|
bufMode = b
|
2014-03-06 17:48:14 +08:00
|
|
|
text := ""
|
2014-01-03 14:47:07 +08:00
|
|
|
switch bs.mode {
|
2014-01-31 20:01:07 +08:00
|
|
|
case modeCommand:
|
2014-03-06 17:48:14 +08:00
|
|
|
text = "Command"
|
|
|
|
b.writes(trimWcwidth("Command", width), attrForMode)
|
2014-03-05 11:22:09 +08:00
|
|
|
case modeCompletion:
|
2014-03-06 17:48:14 +08:00
|
|
|
text = fmt.Sprintf("Completing %s", bs.line[comp.start:comp.end])
|
|
|
|
case modeNavigation:
|
|
|
|
text = "Navigating"
|
2014-02-09 19:53:21 +08:00
|
|
|
case modeHistory:
|
2014-03-06 17:48:14 +08:00
|
|
|
text = fmt.Sprintf("History #%d", bs.history.current)
|
2014-01-03 14:47:07 +08:00
|
|
|
}
|
2014-03-06 17:48:14 +08:00
|
|
|
b.writes(trimWcwidth(text, width), attrForMode)
|
2014-01-03 14:47:07 +08:00
|
|
|
}
|
|
|
|
|
2014-01-09 20:16:27 +08:00
|
|
|
// bufTips
|
2014-01-09 23:51:35 +08:00
|
|
|
// TODO tips is assumed to contain no newlines.
|
2014-01-08 16:14:18 +08:00
|
|
|
if len(bs.tips) > 0 {
|
2014-01-09 20:16:27 +08:00
|
|
|
b := newBuffer(width)
|
|
|
|
bufTips = b
|
2014-01-09 23:51:35 +08:00
|
|
|
b.writes(trimWcwidth(strings.Join(bs.tips, ", "), width), attrForTip)
|
2013-09-20 00:26:19 +08:00
|
|
|
}
|
|
|
|
|
2014-03-06 17:27:03 +08:00
|
|
|
listingHeight := 0
|
|
|
|
// Trim lines and determine the maximum height for bufListing
|
|
|
|
switch {
|
|
|
|
case height >= lines(bufLine, bufMode, bufTips):
|
|
|
|
listingHeight = height - lines(bufLine, bufMode, bufTips)
|
|
|
|
case height >= lines(bufLine, bufTips):
|
|
|
|
bufMode, bufListing = nil, nil
|
|
|
|
case height >= lines(bufLine):
|
|
|
|
bufTips, bufMode, bufListing = nil, nil, nil
|
|
|
|
case height >= 1:
|
|
|
|
bufTips, bufMode, bufListing = nil, nil, nil
|
|
|
|
dotLine := bufLine.dot.line
|
|
|
|
bufLine.trimToLines(dotLine+1-height, dotLine+1)
|
|
|
|
default:
|
|
|
|
bufLine, bufTips, bufMode, bufListing = nil, nil, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render bufListing under the maximum height constraint
|
|
|
|
nav := bs.navigation
|
|
|
|
if listingHeight > 0 && comp != nil || nav != nil {
|
2014-01-09 20:16:27 +08:00
|
|
|
b := newBuffer(width)
|
2014-03-06 01:09:57 +08:00
|
|
|
bufListing = b
|
2014-03-06 17:27:03 +08:00
|
|
|
// Completion listing
|
|
|
|
if comp != nil {
|
|
|
|
// Layout candidates in multiple columns
|
|
|
|
cands := comp.candidates
|
|
|
|
|
|
|
|
// First decide the shape (# of rows and columns)
|
|
|
|
colWidth := 0
|
|
|
|
colMargin := 2
|
|
|
|
for _, cand := range cands {
|
|
|
|
width := wcwidths(cand.text)
|
|
|
|
if colWidth < width {
|
|
|
|
colWidth = width
|
|
|
|
}
|
2013-12-25 16:43:52 +08:00
|
|
|
}
|
2013-12-31 11:28:05 +08:00
|
|
|
|
2014-03-06 17:27:03 +08:00
|
|
|
cols := (b.width + colMargin) / (colWidth + colMargin)
|
|
|
|
if cols == 0 {
|
|
|
|
cols = 1
|
2014-01-09 20:16:27 +08:00
|
|
|
}
|
2014-03-06 17:27:03 +08:00
|
|
|
lines := util.CeilDiv(len(cands), cols)
|
|
|
|
bs.completionLines = lines
|
|
|
|
|
|
|
|
// Determine the window to show.
|
2014-03-06 17:53:28 +08:00
|
|
|
low, high := findWindow(lines, comp.current%lines, listingHeight)
|
2014-03-06 17:27:03 +08:00
|
|
|
for i := low; i < high; i++ {
|
2014-03-06 17:53:28 +08:00
|
|
|
if i > low {
|
2014-03-06 17:27:03 +08:00
|
|
|
b.newline()
|
2013-12-31 11:28:05 +08:00
|
|
|
}
|
2014-03-06 17:27:03 +08:00
|
|
|
for j := 0; j < cols; j++ {
|
|
|
|
k := j*lines + i
|
|
|
|
if k >= len(cands) {
|
|
|
|
continue
|
|
|
|
}
|
2014-03-06 17:53:28 +08:00
|
|
|
attr := ""
|
2014-03-06 17:27:03 +08:00
|
|
|
if k == comp.current {
|
|
|
|
attr = attrForCurrentCompletion
|
|
|
|
}
|
|
|
|
text := cands[k].text
|
|
|
|
b.writes(text, attr)
|
|
|
|
b.writePadding(colWidth-wcwidths(text), attr)
|
|
|
|
b.writePadding(colMargin, "")
|
2013-12-31 11:28:05 +08:00
|
|
|
}
|
2013-12-25 16:43:52 +08:00
|
|
|
}
|
2013-12-24 19:01:01 +08:00
|
|
|
}
|
|
|
|
|
2014-03-06 17:27:03 +08:00
|
|
|
// Navigation listing
|
|
|
|
if nav != nil {
|
|
|
|
b := newBuffer(width)
|
|
|
|
bufListing = b
|
|
|
|
|
2014-03-06 17:43:34 +08:00
|
|
|
filenames, low := trimToWindow(nav.current.names, nav.current.selected, listingHeight)
|
|
|
|
parentFilenames, parentLow := trimToWindow(nav.parent.names, nav.parent.selected, listingHeight)
|
2014-03-06 17:27:03 +08:00
|
|
|
|
|
|
|
// TODO(xiaq): When laying out the navigation listing, determine
|
|
|
|
// the width of two columns more intelligently instead of
|
|
|
|
// allocating half of screen for each. Maybe the algorithm used by
|
|
|
|
// ranger could be pirated.
|
|
|
|
colMargin := 1
|
|
|
|
parentWidth := (width + colMargin) / 2
|
|
|
|
currentWidth := width - colMargin - parentWidth
|
|
|
|
for i := 0; i < len(filenames) || i < len(parentFilenames); i++ {
|
|
|
|
if i > 0 {
|
|
|
|
b.newline()
|
|
|
|
}
|
|
|
|
text, attr := "", ""
|
|
|
|
if i < len(parentFilenames) {
|
|
|
|
text = parentFilenames[i]
|
|
|
|
}
|
2014-03-06 17:43:34 +08:00
|
|
|
if i+parentLow == nav.parent.selected {
|
2014-03-06 01:09:57 +08:00
|
|
|
attr = attrForSelectedFile
|
|
|
|
}
|
2014-03-06 17:27:03 +08:00
|
|
|
b.writes(trimWcwidth(text, parentWidth), attr)
|
|
|
|
b.writePadding(parentWidth-wcwidths(text), attr)
|
|
|
|
b.writePadding(colMargin, "")
|
|
|
|
|
|
|
|
if i < len(filenames) {
|
|
|
|
attr := ""
|
2014-03-06 17:43:34 +08:00
|
|
|
if i+low == nav.current.selected {
|
2014-03-06 17:27:03 +08:00
|
|
|
attr = attrForSelectedFile
|
|
|
|
}
|
|
|
|
text := filenames[i]
|
|
|
|
b.writes(trimWcwidth(text, currentWidth), attr)
|
|
|
|
b.writePadding(currentWidth-wcwidths(text), attr)
|
|
|
|
}
|
2014-03-06 01:09:57 +08:00
|
|
|
}
|
|
|
|
}
|
2014-03-06 17:27:03 +08:00
|
|
|
// Trim bufListing.
|
|
|
|
// XXX This algorithm only works for completion listing.
|
2014-03-06 01:09:57 +08:00
|
|
|
|
2014-01-10 12:51:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Combine buffers (reusing bufLine)
|
2014-01-09 20:16:27 +08:00
|
|
|
buf = bufLine
|
|
|
|
buf.extend(bufMode)
|
|
|
|
buf.extend(bufTips)
|
2014-03-06 01:09:57 +08:00
|
|
|
buf.extend(bufListing)
|
2014-01-10 12:51:20 +08:00
|
|
|
|
2014-01-09 20:16:27 +08:00
|
|
|
return w.commitBuffer(buf)
|
2013-09-19 18:36:47 +08:00
|
|
|
}
|