mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-29 06:12:35 +09:00
More actions and window organization
This commit is contained in:
82
cmd/micro/display/infobar.go
Normal file
82
cmd/micro/display/infobar.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/cmd/micro/buffer"
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
"github.com/zyedidia/micro/cmd/micro/screen"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type InfoBar struct {
|
||||
*buffer.Buffer
|
||||
|
||||
hasPrompt bool
|
||||
hasMessage bool
|
||||
|
||||
message string
|
||||
// style to use when drawing the message
|
||||
style tcell.Style
|
||||
|
||||
width int
|
||||
y int
|
||||
|
||||
// This map stores the history for all the different kinds of uses Prompt has
|
||||
// It's a map of history type -> history array
|
||||
history map[string][]string
|
||||
historyNum int
|
||||
|
||||
// Is the current message a message from the gutter
|
||||
gutterMessage bool
|
||||
}
|
||||
|
||||
func NewInfoBar() *InfoBar {
|
||||
ib := new(InfoBar)
|
||||
ib.style = config.DefStyle
|
||||
ib.history = make(map[string][]string)
|
||||
|
||||
ib.Buffer = buffer.NewBufferFromString("", "infobar")
|
||||
ib.Type = buffer.BTScratch
|
||||
|
||||
ib.width, ib.y = screen.Screen.Size()
|
||||
|
||||
return ib
|
||||
}
|
||||
|
||||
func (i *InfoBar) Clear() {
|
||||
for x := 0; x < i.width; x++ {
|
||||
screen.Screen.SetContent(x, i.y, ' ', nil, config.DefStyle)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoBar) Display() {
|
||||
x := 0
|
||||
if i.hasPrompt || config.GlobalSettings["infobar"].(bool) {
|
||||
display := i.message + strings.TrimSpace(string(i.Bytes()))
|
||||
for _, c := range display {
|
||||
screen.Screen.SetContent(x, i.y, c, nil, i.style)
|
||||
x += runewidth.RuneWidth(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message sends a message to the user
|
||||
func (i *InfoBar) Message(msg ...interface{}) {
|
||||
displayMessage := fmt.Sprint(msg...)
|
||||
// only display a new message if there isn't an active prompt
|
||||
// this is to prevent overwriting an existing prompt to the user
|
||||
if i.hasPrompt == false {
|
||||
// if there is no active prompt then style and display the message as normal
|
||||
i.message = displayMessage
|
||||
i.style = config.DefStyle
|
||||
|
||||
if _, ok := config.Colorscheme["message"]; ok {
|
||||
i.style = config.Colorscheme["message"]
|
||||
}
|
||||
|
||||
i.hasMessage = true
|
||||
}
|
||||
}
|
||||
126
cmd/micro/display/statusline.go
Normal file
126
cmd/micro/display/statusline.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/action"
|
||||
"github.com/zyedidia/micro/cmd/micro/buffer"
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
"github.com/zyedidia/micro/cmd/micro/screen"
|
||||
)
|
||||
|
||||
// StatusLine represents the information line at the bottom
|
||||
// of each window
|
||||
// It gives information such as filename, whether the file has been
|
||||
// modified, filetype, cursor location
|
||||
type StatusLine struct {
|
||||
FormatLeft string
|
||||
FormatRight string
|
||||
Info map[string]func(*buffer.Buffer) string
|
||||
|
||||
win *BufWindow
|
||||
}
|
||||
|
||||
// TODO: plugin modify status line formatter
|
||||
|
||||
// NewStatusLine returns a statusline bound to a window
|
||||
func NewStatusLine(win *BufWindow) *StatusLine {
|
||||
s := new(StatusLine)
|
||||
s.FormatLeft = "$(filename) $(modified)($(line),$(col)) $(opt:filetype) $(opt:fileformat)"
|
||||
// s.FormatLeft = "$(filename) $(modified)(line,col) $(opt:filetype) $(opt:fileformat)"
|
||||
s.FormatRight = "$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp): open help"
|
||||
s.Info = map[string]func(*buffer.Buffer) string{
|
||||
"filename": func(b *buffer.Buffer) string {
|
||||
if b.Settings["basename"].(bool) {
|
||||
return path.Base(b.GetName())
|
||||
}
|
||||
return b.GetName()
|
||||
},
|
||||
"line": func(b *buffer.Buffer) string {
|
||||
return strconv.Itoa(b.GetActiveCursor().Y + 1)
|
||||
},
|
||||
"col": func(b *buffer.Buffer) string {
|
||||
return strconv.Itoa(b.GetActiveCursor().X + 1)
|
||||
},
|
||||
"modified": func(b *buffer.Buffer) string {
|
||||
if b.Modified() {
|
||||
return "+ "
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
s.win = win
|
||||
return s
|
||||
}
|
||||
|
||||
// FindOpt finds a given option in the current buffer's settings
|
||||
func (s *StatusLine) FindOpt(opt string) interface{} {
|
||||
if val, ok := s.win.Buf.Settings[opt]; ok {
|
||||
return val
|
||||
}
|
||||
return "null"
|
||||
}
|
||||
|
||||
var formatParser = regexp.MustCompile(`\$\(.+?\)`)
|
||||
|
||||
// Display draws the statusline to the screen
|
||||
func (s *StatusLine) Display() {
|
||||
// TODO: don't display if infobar off and has message
|
||||
// if !GetGlobalOption("infobar").(bool) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// We'll draw the line at the lowest line in the window
|
||||
y := s.win.Height + s.win.Y - 1
|
||||
|
||||
formatter := func(match []byte) []byte {
|
||||
name := match[2 : len(match)-1]
|
||||
if bytes.HasPrefix(name, []byte("opt")) {
|
||||
option := name[4:]
|
||||
return []byte(fmt.Sprint(s.FindOpt(string(option))))
|
||||
} else if bytes.HasPrefix(name, []byte("bind")) {
|
||||
binding := string(name[5:])
|
||||
for k, v := range action.Bindings {
|
||||
if v == binding {
|
||||
return []byte(k)
|
||||
}
|
||||
}
|
||||
return []byte("null")
|
||||
} else {
|
||||
return []byte(s.Info[string(name)](s.win.Buf))
|
||||
}
|
||||
}
|
||||
|
||||
leftText := []byte(s.FormatLeft)
|
||||
leftText = formatParser.ReplaceAllFunc([]byte(s.FormatLeft), formatter)
|
||||
rightText := []byte(s.FormatRight)
|
||||
rightText = formatParser.ReplaceAllFunc([]byte(s.FormatRight), formatter)
|
||||
|
||||
statusLineStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
}
|
||||
|
||||
leftLen := utf8.RuneCount(leftText)
|
||||
rightLen := utf8.RuneCount(rightText)
|
||||
|
||||
winX := s.win.X
|
||||
for x := 0; x < s.win.Width; x++ {
|
||||
if x < leftLen {
|
||||
r, size := utf8.DecodeRune(leftText)
|
||||
leftText = leftText[size:]
|
||||
screen.Screen.SetContent(winX+x, y, r, nil, statusLineStyle)
|
||||
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
|
||||
r, size := utf8.DecodeRune(rightText)
|
||||
rightText = rightText[size:]
|
||||
screen.Screen.SetContent(winX+x, y, r, nil, statusLineStyle)
|
||||
} else {
|
||||
screen.Screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
239
cmd/micro/display/window.go
Normal file
239
cmd/micro/display/window.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/cmd/micro/buffer"
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
"github.com/zyedidia/micro/cmd/micro/screen"
|
||||
"github.com/zyedidia/micro/cmd/micro/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type Window interface {
|
||||
Display()
|
||||
Clear()
|
||||
}
|
||||
|
||||
type BufWindow struct {
|
||||
// X and Y coordinates for the top left of the window
|
||||
X int
|
||||
Y int
|
||||
|
||||
// Width and Height for the window
|
||||
Width int
|
||||
Height int
|
||||
|
||||
// Which line in the buffer to start displaying at (vertical scroll)
|
||||
StartLine int
|
||||
// Which visual column in the to start displaying at (horizontal scroll)
|
||||
StartCol int
|
||||
|
||||
// Buffer being shown in this window
|
||||
Buf *buffer.Buffer
|
||||
|
||||
sline *StatusLine
|
||||
}
|
||||
|
||||
func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
|
||||
w := new(BufWindow)
|
||||
w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
|
||||
|
||||
w.sline = NewStatusLine(w)
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *BufWindow) Clear() {
|
||||
for y := 0; y < w.Height; y++ {
|
||||
for x := 0; x < w.Width; x++ {
|
||||
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
lineNum := strconv.Itoa(bloc.Y + 1)
|
||||
|
||||
// Write the spaces before the line number if necessary
|
||||
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
vloc.X++
|
||||
}
|
||||
// Write the actual line number
|
||||
for _, ch := range lineNum {
|
||||
if softwrapped {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
} else {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
|
||||
}
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
// Write the extra space
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
// getStyle returns the highlight style for the given character position
|
||||
// If there is no change to the current highlight style it just returns that
|
||||
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) tcell.Style {
|
||||
if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
|
||||
s := config.GetColor(group.String())
|
||||
return s
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
func (w *BufWindow) showCursor(x, y int, main bool) {
|
||||
if main {
|
||||
screen.Screen.ShowCursor(x, y)
|
||||
} else {
|
||||
r, _, _, _ := screen.Screen.GetContent(x, y)
|
||||
screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
}
|
||||
|
||||
// displayBuffer draws the buffer being shown in this window on the screen.Screen
|
||||
func (w *BufWindow) displayBuffer() {
|
||||
b := w.Buf
|
||||
|
||||
bufHeight := w.Height
|
||||
if b.Settings["statusline"].(bool) {
|
||||
bufHeight--
|
||||
}
|
||||
|
||||
// TODO: Rehighlighting
|
||||
// start := w.StartLine
|
||||
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
|
||||
// if start > 0 && b.lines[start-1].rehighlight {
|
||||
// b.highlighter.ReHighlightLine(b, start-1)
|
||||
// b.lines[start-1].rehighlight = false
|
||||
// }
|
||||
//
|
||||
// b.highlighter.ReHighlightStates(b, start)
|
||||
//
|
||||
b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
|
||||
}
|
||||
|
||||
lineNumStyle := config.DefStyle
|
||||
if style, ok := config.Colorscheme["line-number"]; ok {
|
||||
lineNumStyle = style
|
||||
}
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
// so we can pad appropriately when displaying line numbers
|
||||
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
|
||||
|
||||
tabsize := int(b.Settings["tabsize"].(float64))
|
||||
softwrap := b.Settings["softwrap"].(bool)
|
||||
|
||||
// this represents the current draw position
|
||||
// within the current window
|
||||
vloc := buffer.Loc{0, 0}
|
||||
|
||||
// this represents the current draw position in the buffer (char positions)
|
||||
bloc := buffer.Loc{w.StartCol, w.StartLine}
|
||||
|
||||
activeC := w.Buf.GetActiveCursor()
|
||||
|
||||
curStyle := config.DefStyle
|
||||
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||
vloc.X = 0
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, false, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
|
||||
line := b.LineBytes(bloc.Y)
|
||||
line, nColsBeforeStart := util.SliceVisualEnd(line, bloc.X, tabsize)
|
||||
|
||||
draw := func(r rune, style tcell.Style) {
|
||||
if nColsBeforeStart <= 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
|
||||
style = config.DefStyle.Reverse(true)
|
||||
|
||||
if s, ok := config.Colorscheme["selection"]; ok {
|
||||
style = s
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
|
||||
vloc.X++
|
||||
}
|
||||
nColsBeforeStart--
|
||||
}
|
||||
|
||||
totalwidth := bloc.X - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
if activeC.X == bloc.X && activeC.Y == bloc.Y {
|
||||
w.showCursor(vloc.X, vloc.Y, true)
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(line)
|
||||
curStyle = w.getStyle(curStyle, bloc, r)
|
||||
|
||||
draw(r, curStyle)
|
||||
|
||||
width := 0
|
||||
|
||||
char := ' '
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
width = ts
|
||||
default:
|
||||
width = runewidth.RuneWidth(r)
|
||||
char = '@'
|
||||
}
|
||||
|
||||
bloc.X++
|
||||
line = line[size:]
|
||||
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for i := 1; i < width; i++ {
|
||||
draw(char, curStyle)
|
||||
}
|
||||
}
|
||||
totalwidth += width
|
||||
|
||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||
if vloc.X >= w.Width {
|
||||
if !softwrap {
|
||||
break
|
||||
} else {
|
||||
vloc.Y++
|
||||
if vloc.Y >= bufHeight {
|
||||
break
|
||||
}
|
||||
vloc.X = 0
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
if activeC.X == bloc.X && activeC.Y == bloc.Y {
|
||||
w.showCursor(vloc.X, vloc.Y, true)
|
||||
}
|
||||
bloc.X = w.StartCol
|
||||
bloc.Y++
|
||||
if bloc.Y >= b.LinesNum() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) displayStatusLine() {
|
||||
w.sline.Display()
|
||||
}
|
||||
|
||||
func (w *BufWindow) Display() {
|
||||
w.displayBuffer()
|
||||
w.displayStatusLine()
|
||||
}
|
||||
Reference in New Issue
Block a user