2019-01-01 06:07:01 +03:00
|
|
|
package display
|
|
|
|
|
|
|
|
import (
|
|
|
|
runewidth "github.com/mattn/go-runewidth"
|
2020-05-04 17:16:15 +03:00
|
|
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
|
|
|
"github.com/zyedidia/micro/v2/internal/config"
|
|
|
|
"github.com/zyedidia/micro/v2/internal/info"
|
|
|
|
"github.com/zyedidia/micro/v2/internal/screen"
|
|
|
|
"github.com/zyedidia/micro/v2/internal/util"
|
2020-01-01 04:15:45 +03:00
|
|
|
"github.com/zyedidia/tcell"
|
2019-01-01 06:07:01 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
type InfoWindow struct {
|
2019-01-02 06:36:12 +03:00
|
|
|
*info.InfoBuf
|
2019-01-01 06:07:01 +03:00
|
|
|
*View
|
2020-02-03 04:17:46 +03:00
|
|
|
|
|
|
|
hscroll int
|
2019-01-01 06:07:01 +03:00
|
|
|
}
|
|
|
|
|
2019-06-16 19:45:39 +03:00
|
|
|
func (i *InfoWindow) errStyle() tcell.Style {
|
|
|
|
errStyle := config.DefStyle.
|
|
|
|
Foreground(tcell.ColorBlack).
|
|
|
|
Background(tcell.ColorMaroon)
|
2019-01-01 06:07:01 +03:00
|
|
|
|
2019-06-16 19:45:39 +03:00
|
|
|
if _, ok := config.Colorscheme["error-message"]; ok {
|
|
|
|
errStyle = config.Colorscheme["error-message"]
|
|
|
|
}
|
|
|
|
|
|
|
|
return errStyle
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *InfoWindow) defStyle() tcell.Style {
|
|
|
|
defStyle := config.DefStyle
|
2019-01-01 06:07:01 +03:00
|
|
|
|
|
|
|
if _, ok := config.Colorscheme["message"]; ok {
|
2019-06-16 19:45:39 +03:00
|
|
|
defStyle = config.Colorscheme["message"]
|
2019-01-01 06:07:01 +03:00
|
|
|
}
|
|
|
|
|
2019-06-16 19:45:39 +03:00
|
|
|
return defStyle
|
|
|
|
}
|
2019-01-01 06:07:01 +03:00
|
|
|
|
2019-06-16 19:45:39 +03:00
|
|
|
func NewInfoWindow(b *info.InfoBuf) *InfoWindow {
|
|
|
|
iw := new(InfoWindow)
|
|
|
|
iw.InfoBuf = b
|
|
|
|
iw.View = new(View)
|
2019-01-01 06:07:01 +03:00
|
|
|
|
2019-01-17 02:37:45 +03:00
|
|
|
iw.Width, iw.Y = screen.Screen.Size()
|
|
|
|
iw.Y--
|
2019-01-01 06:07:01 +03:00
|
|
|
|
|
|
|
return iw
|
|
|
|
}
|
|
|
|
|
2019-01-05 01:40:56 +03:00
|
|
|
func (i *InfoWindow) Resize(w, h int) {
|
2019-01-17 02:37:45 +03:00
|
|
|
i.Width = w
|
|
|
|
i.Y = h
|
2019-01-05 01:40:56 +03:00
|
|
|
}
|
|
|
|
|
2019-01-14 08:57:39 +03:00
|
|
|
func (i *InfoWindow) SetBuffer(b *buffer.Buffer) {
|
|
|
|
i.InfoBuf.Buffer = b
|
|
|
|
}
|
|
|
|
|
2019-01-05 02:08:11 +03:00
|
|
|
func (i *InfoWindow) Relocate() bool { return false }
|
|
|
|
func (i *InfoWindow) GetView() *View { return i.View }
|
|
|
|
func (i *InfoWindow) SetView(v *View) {}
|
|
|
|
func (i *InfoWindow) SetActive(b bool) {}
|
2019-08-04 09:53:33 +03:00
|
|
|
func (i *InfoWindow) IsActive() bool { return true }
|
2019-01-01 06:07:01 +03:00
|
|
|
|
2019-12-25 00:01:08 +03:00
|
|
|
func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
2019-01-03 01:39:50 +03:00
|
|
|
c := i.Buffer.GetActiveCursor()
|
|
|
|
l := i.Buffer.LineBytes(0)
|
2020-05-20 23:47:08 +03:00
|
|
|
n := util.CharacterCountInString(i.Msg)
|
2019-01-03 01:39:50 +03:00
|
|
|
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
|
|
|
|
}
|
|
|
|
|
2019-01-01 06:07:01 +03:00
|
|
|
func (i *InfoWindow) Clear() {
|
2019-01-17 02:37:45 +03:00
|
|
|
for x := 0; x < i.Width; x++ {
|
2020-01-02 06:40:51 +03:00
|
|
|
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
2019-01-01 06:07:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-01 07:47:24 +03:00
|
|
|
func (i *InfoWindow) displayBuffer() {
|
|
|
|
b := i.Buffer
|
|
|
|
line := b.LineBytes(0)
|
|
|
|
activeC := b.GetActiveCursor()
|
|
|
|
|
|
|
|
blocX := 0
|
2020-05-20 23:47:08 +03:00
|
|
|
vlocX := util.CharacterCountInString(i.Msg)
|
2019-01-01 07:47:24 +03:00
|
|
|
|
|
|
|
tabsize := 4
|
2019-01-03 04:07:48 +03:00
|
|
|
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
|
|
|
|
blocX = bslice
|
2019-01-01 07:47:24 +03:00
|
|
|
|
2020-05-20 23:43:12 +03:00
|
|
|
draw := func(r rune, combc []rune, style tcell.Style) {
|
2019-01-01 07:47:24 +03:00
|
|
|
if nColsBeforeStart <= 0 {
|
|
|
|
bloc := buffer.Loc{X: blocX, Y: 0}
|
|
|
|
if activeC.HasSelection() &&
|
|
|
|
(bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) ||
|
|
|
|
bloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) {
|
|
|
|
// The current character is selected
|
2019-06-16 19:45:39 +03:00
|
|
|
style = i.defStyle().Reverse(true)
|
2019-01-01 07:47:24 +03:00
|
|
|
|
|
|
|
if s, ok := config.Colorscheme["selection"]; ok {
|
|
|
|
style = s
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-06-15 23:54:53 +03:00
|
|
|
rw := runewidth.RuneWidth(r)
|
|
|
|
for j := 0; j < rw; j++ {
|
|
|
|
c := r
|
|
|
|
if j > 0 {
|
|
|
|
c = ' '
|
2020-05-20 23:43:12 +03:00
|
|
|
combc = nil
|
2019-06-15 23:54:53 +03:00
|
|
|
}
|
2020-05-20 23:43:12 +03:00
|
|
|
screen.SetContent(vlocX, i.Y, c, combc, style)
|
2019-06-15 23:54:53 +03:00
|
|
|
}
|
2019-01-01 07:47:24 +03:00
|
|
|
vlocX++
|
|
|
|
}
|
|
|
|
nColsBeforeStart--
|
|
|
|
}
|
|
|
|
|
|
|
|
totalwidth := blocX - nColsBeforeStart
|
|
|
|
for len(line) > 0 {
|
2020-02-11 03:55:13 +03:00
|
|
|
curVX := vlocX
|
|
|
|
curBX := blocX
|
2020-05-20 23:43:12 +03:00
|
|
|
r, combc, size := util.DecodeCharacter(line)
|
2019-01-01 07:47:24 +03:00
|
|
|
|
2020-05-20 23:43:12 +03:00
|
|
|
draw(r, combc, i.defStyle())
|
2019-01-01 07:47:24 +03:00
|
|
|
|
|
|
|
width := 0
|
|
|
|
|
|
|
|
char := ' '
|
|
|
|
switch r {
|
|
|
|
case '\t':
|
|
|
|
ts := tabsize - (totalwidth % tabsize)
|
|
|
|
width = ts
|
|
|
|
default:
|
|
|
|
width = runewidth.RuneWidth(r)
|
|
|
|
char = '@'
|
|
|
|
}
|
|
|
|
|
|
|
|
blocX++
|
|
|
|
line = line[size:]
|
|
|
|
|
|
|
|
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
|
|
|
if width > 1 {
|
|
|
|
for j := 1; j < width; j++ {
|
2020-05-20 23:43:12 +03:00
|
|
|
draw(char, nil, i.defStyle())
|
2019-01-01 07:47:24 +03:00
|
|
|
}
|
|
|
|
}
|
2020-02-11 03:55:13 +03:00
|
|
|
if activeC.X == curBX {
|
|
|
|
screen.ShowCursor(curVX, i.Y)
|
|
|
|
}
|
2019-01-01 07:47:24 +03:00
|
|
|
totalwidth += width
|
2019-01-17 02:37:45 +03:00
|
|
|
if vlocX >= i.Width {
|
2019-01-01 07:47:24 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if activeC.X == blocX {
|
2020-01-02 05:29:18 +03:00
|
|
|
screen.ShowCursor(vlocX, i.Y)
|
2019-01-17 02:37:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-21 01:49:20 +03:00
|
|
|
var keydisplay = []string{"^Q Quit, ^S Save, ^O Open, ^G Help, ^E Command Bar, ^K Cut Line", "^F Find, ^Z Undo, ^Y Redo, ^A Select All, ^D Duplicate Line, ^T New Tab"}
|
|
|
|
|
2019-01-17 02:37:45 +03:00
|
|
|
func (i *InfoWindow) displayKeyMenu() {
|
|
|
|
// TODO: maybe make this based on the actual keybindings
|
|
|
|
|
2019-01-21 01:49:20 +03:00
|
|
|
for y := 0; y < len(keydisplay); y++ {
|
2019-01-17 02:37:45 +03:00
|
|
|
for x := 0; x < i.Width; x++ {
|
2019-01-21 01:49:20 +03:00
|
|
|
if x < len(keydisplay[y]) {
|
2020-01-02 06:40:51 +03:00
|
|
|
screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
|
2019-01-17 02:37:45 +03:00
|
|
|
} else {
|
2020-01-02 06:40:51 +03:00
|
|
|
screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
|
2019-01-17 02:37:45 +03:00
|
|
|
}
|
|
|
|
}
|
2019-01-01 07:47:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-03 04:17:46 +03:00
|
|
|
func (i *InfoWindow) totalSize() int {
|
|
|
|
sum := 0
|
|
|
|
for _, n := range i.Suggestions {
|
|
|
|
sum += runewidth.StringWidth(n) + 1
|
|
|
|
}
|
|
|
|
return sum
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *InfoWindow) scrollToSuggestion() {
|
|
|
|
x := 0
|
|
|
|
s := i.totalSize()
|
|
|
|
|
|
|
|
for j, n := range i.Suggestions {
|
2020-05-20 23:47:08 +03:00
|
|
|
c := util.CharacterCountInString(n)
|
2020-02-03 04:17:46 +03:00
|
|
|
if j == i.CurSuggestion {
|
|
|
|
if x+c >= i.hscroll+i.Width {
|
|
|
|
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
|
|
|
|
} else if x < i.hscroll {
|
|
|
|
i.hscroll = util.Clamp(x-1, 0, s-i.Width)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
x += c + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if s-i.Width <= 0 {
|
|
|
|
i.hscroll = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-01 06:07:01 +03:00
|
|
|
func (i *InfoWindow) Display() {
|
|
|
|
if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
|
2020-03-17 21:21:36 +03:00
|
|
|
i.Clear()
|
|
|
|
x := 0
|
|
|
|
if config.GetGlobalOption("keymenu").(bool) {
|
|
|
|
i.displayKeyMenu()
|
|
|
|
}
|
|
|
|
|
2019-01-01 07:47:24 +03:00
|
|
|
if !i.HasPrompt && !i.HasMessage && !i.HasError {
|
|
|
|
return
|
|
|
|
}
|
2019-01-15 06:16:44 +03:00
|
|
|
i.Clear()
|
2019-06-16 19:45:39 +03:00
|
|
|
style := i.defStyle()
|
2019-01-01 06:07:01 +03:00
|
|
|
|
|
|
|
if i.HasError {
|
2019-06-16 19:45:39 +03:00
|
|
|
style = i.errStyle()
|
2019-01-01 06:07:01 +03:00
|
|
|
}
|
|
|
|
|
2019-01-01 07:47:24 +03:00
|
|
|
display := i.Msg
|
2019-01-01 06:07:01 +03:00
|
|
|
for _, c := range display {
|
2020-01-02 06:40:51 +03:00
|
|
|
screen.SetContent(x, i.Y, c, nil, style)
|
2019-01-01 06:07:01 +03:00
|
|
|
x += runewidth.RuneWidth(c)
|
|
|
|
}
|
2019-01-01 07:47:24 +03:00
|
|
|
|
|
|
|
if i.HasPrompt {
|
|
|
|
i.displayBuffer()
|
|
|
|
}
|
2019-01-01 06:07:01 +03:00
|
|
|
}
|
2019-01-21 01:49:20 +03:00
|
|
|
|
2019-01-25 02:09:57 +03:00
|
|
|
if i.HasSuggestions && len(i.Suggestions) > 1 {
|
2020-02-03 04:17:46 +03:00
|
|
|
i.scrollToSuggestion()
|
|
|
|
|
|
|
|
x := -i.hscroll
|
|
|
|
done := false
|
|
|
|
|
2019-01-21 01:49:20 +03:00
|
|
|
statusLineStyle := config.DefStyle.Reverse(true)
|
|
|
|
if style, ok := config.Colorscheme["statusline"]; ok {
|
|
|
|
statusLineStyle = style
|
|
|
|
}
|
|
|
|
keymenuOffset := 0
|
|
|
|
if config.GetGlobalOption("keymenu").(bool) {
|
|
|
|
keymenuOffset = len(keydisplay)
|
|
|
|
}
|
2020-02-03 04:17:46 +03:00
|
|
|
|
|
|
|
draw := func(r rune, s tcell.Style) {
|
|
|
|
y := i.Y - keymenuOffset - 1
|
|
|
|
rw := runewidth.RuneWidth(r)
|
|
|
|
for j := 0; j < rw; j++ {
|
|
|
|
c := r
|
|
|
|
if j > 0 {
|
|
|
|
c = ' '
|
|
|
|
}
|
|
|
|
|
|
|
|
if x == i.Width-1 && !done {
|
|
|
|
screen.SetContent(i.Width-1, y, '>', nil, s)
|
|
|
|
x++
|
|
|
|
break
|
|
|
|
} else if x == 0 && i.hscroll > 0 {
|
|
|
|
screen.SetContent(0, y, '<', nil, s)
|
|
|
|
} else if x >= 0 && x < i.Width {
|
|
|
|
screen.SetContent(x, y, c, nil, s)
|
|
|
|
}
|
|
|
|
x++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-25 02:09:57 +03:00
|
|
|
for j, s := range i.Suggestions {
|
|
|
|
style := statusLineStyle
|
|
|
|
if i.CurSuggestion == j {
|
|
|
|
style = style.Reverse(true)
|
|
|
|
}
|
2019-01-21 01:49:20 +03:00
|
|
|
for _, r := range s {
|
2020-02-03 04:17:46 +03:00
|
|
|
draw(r, style)
|
|
|
|
// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
2019-01-21 01:49:20 +03:00
|
|
|
}
|
2020-02-03 04:17:46 +03:00
|
|
|
draw(' ', statusLineStyle)
|
2019-01-21 01:49:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for x < i.Width {
|
2020-02-03 04:17:46 +03:00
|
|
|
draw(' ', statusLineStyle)
|
2019-01-21 01:49:20 +03:00
|
|
|
}
|
|
|
|
}
|
2019-01-01 06:07:01 +03:00
|
|
|
}
|