Add infobar

This commit is contained in:
Zachary Yedidia
2018-12-31 22:07:01 -05:00
parent e9a4238a3f
commit c50e0cb932
11 changed files with 309 additions and 135 deletions

View File

@@ -7,34 +7,45 @@ import (
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/buffer"
"github.com/zyedidia/micro/cmd/micro/info"
"github.com/zyedidia/micro/cmd/micro/screen"
"github.com/zyedidia/micro/cmd/micro/util"
"github.com/zyedidia/tcell"
)
// ScrollUp is not an action
func (h *BufHandler) ScrollUp(n int) {
v := h.Win.GetView()
if v.StartLine >= n {
v.StartLine -= n
h.Win.SetView(v)
}
}
// ScrollDown is not an action
func (h *BufHandler) ScrollDown(n int) {
v := h.Win.GetView()
if v.StartLine <= h.Buf.LinesNum()-1-n {
v.StartLine += n
h.Win.SetView(v)
}
}
// MousePress is the event that should happen when a normal click happens
// This is almost always bound to left click
func (h *BufHandler) MousePress(e *tcell.EventMouse) bool {
h.ScrollUp(h.Buf.Settings["scrollspeed"].(int))
return false
}
// ScrollUpAction scrolls the view up
func (h *BufHandler) ScrollUpAction() bool {
b := h.Buf
sspeed := b.Settings["scrollspeed"].(int)
if h.Win.StartLine >= sspeed {
h.Win.StartLine -= sspeed
}
return false
}
// ScrollDownAction scrolls the view up
func (h *BufHandler) ScrollDownAction() bool {
b := h.Buf
sspeed := b.Settings["scrollspeed"].(int)
if h.Win.StartLine <= h.Buf.LinesNum()-1-sspeed {
h.Win.StartLine += sspeed
}
h.ScrollDown(h.Buf.Settings["scrollspeed"].(int))
return false
}
@@ -514,7 +525,7 @@ func (h *BufHandler) CutLine() bool {
h.lastCutTime = time.Now()
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
// messenger.Message("Cut line")
info.MainBar.Message("Cut line")
return true
}
@@ -525,7 +536,7 @@ func (h *BufHandler) Cut() bool {
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = true
// messenger.Message("Cut selection")
info.MainBar.Message("Cut selection")
return true
} else {
@@ -543,7 +554,7 @@ func (h *BufHandler) DuplicateLine() bool {
// h.Cursor.Right()
}
// messenger.Message("Duplicated line")
info.MainBar.Message("Duplicated line")
return true
}
@@ -555,7 +566,7 @@ func (h *BufHandler) DeleteLine() bool {
}
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
// messenger.Message("Deleted line")
info.MainBar.Message("Deleted line")
return true
}
@@ -603,17 +614,21 @@ func (h *BufHandler) OpenFile() bool {
// Start moves the viewport to the start of the buffer
func (h *BufHandler) Start() bool {
h.Win.StartLine = 0
v := h.Win.GetView()
v.StartLine = 0
h.Win.SetView(v)
return false
}
// End moves the viewport to the end of the buffer
func (h *BufHandler) End() bool {
// TODO: softwrap problems?
if h.Win.Height > h.Buf.LinesNum() {
h.Win.StartLine = 0
v := h.Win.GetView()
if v.Height > h.Buf.LinesNum() {
v.StartLine = 0
h.Win.SetView(v)
} else {
h.StartLine = h.Buf.LinesNum() - h.Win.Height
h.StartLine = h.Buf.LinesNum() - v.Height
}
return false
}
@@ -662,10 +677,10 @@ func (h *BufHandler) HalfPageDown() bool {
func (h *BufHandler) ToggleRuler() bool {
if !h.Buf.Settings["ruler"].(bool) {
h.Buf.Settings["ruler"] = true
// messenger.Message("Enabled ruler")
info.MainBar.Message("Enabled ruler")
} else {
h.Buf.Settings["ruler"] = false
// messenger.Message("Disabled ruler")
info.MainBar.Message("Disabled ruler")
}
return false
}

View File

@@ -47,7 +47,7 @@ func BufMapMouse(k MouseEvent, action string) {
// visual positions for mouse clicks and scrolling
type BufHandler struct {
Buf *buffer.Buffer
Win *display.BufWindow
Win display.Window
cursors []*buffer.Cursor
Cursor *buffer.Cursor // the active cursor
@@ -83,7 +83,7 @@ type BufHandler struct {
tripleClick bool
}
func NewBufHandler(buf *buffer.Buffer, win *display.BufWindow) *BufHandler {
func NewBufHandler(buf *buffer.Buffer, win display.Window) *BufHandler {
h := new(BufHandler)
h.Buf = buf
h.Win = win

View File

@@ -53,14 +53,15 @@ type BufType struct {
Kind int
Readonly bool // The file cannot be edited
Scratch bool // The file cannot be saved
Syntax bool // Syntax highlighting is enabled
}
var (
BTDefault = BufType{0, false, false}
BTHelp = BufType{1, true, true}
BTLog = BufType{2, true, true}
BTScratch = BufType{3, false, true}
BTRaw = BufType{4, true, true}
BTDefault = BufType{0, false, false, true}
BTHelp = BufType{1, true, true, true}
BTLog = BufType{2, true, true, false}
BTScratch = BufType{3, false, true, false}
BTRaw = BufType{4, true, true, false}
)
// Buffer stores the main information about a currently open file including
@@ -107,7 +108,7 @@ type Buffer struct {
// It will also automatically handle `~`, and line/column with filename:l:c
// It will return an empty buffer if the path does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string) (*Buffer, error) {
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
var err error
filename, cursorPosition := GetPathAndCursorPosition(path)
filename, err = ReplaceHome(filename)
@@ -127,24 +128,25 @@ func NewBufferFromFile(path string) (*Buffer, error) {
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", filename)
buf = NewBufferFromString("", filename, btype)
} else {
buf = NewBuffer(file, FSize(file), filename, cursorPosition)
buf = NewBuffer(file, FSize(file), filename, cursorPosition, btype)
}
return buf, nil
}
// NewBufferFromString creates a new buffer containing the given string
func NewBufferFromString(text, path string) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil)
func NewBufferFromString(text, path string, btype BufType) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil, btype)
}
// NewBuffer creates a new buffer from a given reader with a given path
// Ensure that ReadSettings and InitGlobalSettings have been called before creating
// a new buffer
func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string, btype BufType) *Buffer {
b := new(Buffer)
b.Type = btype
b.Settings = config.DefaultLocalSettings()
for k, v := range config.GlobalSettings {
@@ -344,6 +346,9 @@ func (b *Buffer) deleteToEnd(start Loc) {
// UpdateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes
func (b *Buffer) UpdateRules() {
if !b.Type.Syntax {
return
}
rehighlight := false
var files []*highlight.File
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {

View File

@@ -1,84 +0,0 @@
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"
)
// The InfoBar displays messages and other info at the bottom of the screen.
// It is respresented as a buffer and a message with a style.
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
}
}

View File

@@ -0,0 +1,74 @@
package display
import (
"strings"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/cmd/micro/config"
"github.com/zyedidia/micro/cmd/micro/info"
"github.com/zyedidia/micro/cmd/micro/screen"
"github.com/zyedidia/tcell"
)
type InfoWindow struct {
*info.Bar
*View
defStyle tcell.Style
errStyle tcell.Style
width int
y int
}
func NewInfoWindow(b *info.Bar) *InfoWindow {
iw := new(InfoWindow)
iw.Bar = b
iw.View = new(View)
iw.defStyle = config.DefStyle
if _, ok := config.Colorscheme["message"]; ok {
iw.defStyle = config.Colorscheme["message"]
}
iw.errStyle = config.DefStyle.
Foreground(tcell.ColorBlack).
Background(tcell.ColorMaroon)
if _, ok := config.Colorscheme["error-message"]; ok {
iw.errStyle = config.Colorscheme["error-message"]
}
iw.width, iw.y = screen.Screen.Size()
iw.y--
return iw
}
func (i *InfoWindow) Relocate() bool { return false }
func (i *InfoWindow) GetView() *View { return i.View }
func (i *InfoWindow) SetView(v *View) {}
func (i *InfoWindow) Clear() {
for x := 0; x < i.width; x++ {
screen.Screen.SetContent(x, i.y, ' ', nil, config.DefStyle)
}
}
func (i *InfoWindow) Display() {
x := 0
if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
style := i.defStyle
if i.HasError {
style = i.errStyle
}
display := i.Msg + strings.TrimSpace(string(i.Bytes()))
for _, c := range display {
screen.Screen.SetContent(x, i.y, c, nil, style)
x += runewidth.RuneWidth(c)
}
}
}

View File

@@ -13,26 +13,24 @@ import (
"github.com/zyedidia/tcell"
)
type View struct {
X, Y int // X,Y location of the view
Width, Height int // Width and height of the view
StartLine, StartCol int // Start line and start column of the view (vertical/horizontal scroll)
}
type Window interface {
Display()
Clear()
Relocate() bool
GetView() *View
SetView(v *View)
}
// The BufWindow provides a way of displaying a certain section
// of a buffer
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
*View
// Buffer being shown in this window
Buf *buffer.Buffer
@@ -45,6 +43,7 @@ type BufWindow struct {
// NewBufWindow creates a new window at a location in the screen with a width and height
func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
w := new(BufWindow)
w.View = new(View)
w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
w.lineHeight = make([]int, height)
@@ -53,6 +52,14 @@ func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
return w
}
func (v *View) GetView() *View {
return v
}
func (v *View) SetView(view *View) {
v = view
}
// Clear resets all cells in this window to the default style
func (w *BufWindow) Clear() {
for y := 0; y < w.Height; y++ {

18
cmd/micro/info/gutter.go Normal file
View File

@@ -0,0 +1,18 @@
package info
// A GutterMessage is a message displayed on the side of the editor
type GutterMessage struct {
lineNum int
msg string
kind int
}
// These are the different types of messages
const (
// GutterInfo represents a simple info message
GutterInfo = iota
// GutterWarning represents a compiler warning
GutterWarning
// GutterError represents a compiler error
GutterError
)

61
cmd/micro/info/history.go Normal file
View File

@@ -0,0 +1,61 @@
package info
import (
"encoding/gob"
"os"
"github.com/zyedidia/micro/cmd/micro/config"
)
// LoadHistory attempts to load user history from configDir/buffers/history
// into the history map
// The savehistory option must be on
func (i *Bar) LoadHistory() {
if config.GetGlobalOption("savehistory").(bool) {
file, err := os.Open(config.ConfigDir + "/buffers/history")
defer file.Close()
var decodedMap map[string][]string
if err == nil {
decoder := gob.NewDecoder(file)
err = decoder.Decode(&decodedMap)
if err != nil {
i.Error("Error loading history:", err)
return
}
}
if decodedMap != nil {
i.History = decodedMap
} else {
i.History = make(map[string][]string)
}
} else {
i.History = make(map[string][]string)
}
}
// SaveHistory saves the user's command history to configDir/buffers/history
// only if the savehistory option is on
func (i *Bar) SaveHistory() {
if config.GetGlobalOption("savehistory").(bool) {
// Don't save history past 100
for k, v := range i.History {
if len(v) > 100 {
i.History[k] = v[len(i.History[k])-100:]
}
}
file, err := os.Create(config.ConfigDir + "/buffers/history")
defer file.Close()
if err == nil {
encoder := gob.NewEncoder(file)
err = encoder.Encode(i.History)
if err != nil {
i.Error("Error saving history:", err)
return
}
}
}
}

66
cmd/micro/info/infobar.go Normal file
View File

@@ -0,0 +1,66 @@
package info
import (
"fmt"
"github.com/zyedidia/micro/cmd/micro/buffer"
)
var MainBar *Bar
func InitInfoBar() {
MainBar = NewBar()
}
// The Bar displays messages and other info at the bottom of the screen.
// It is respresented as a buffer and a message with a style.
type Bar struct {
*buffer.Buffer
HasPrompt bool
HasMessage bool
HasError bool
Msg string
// 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 NewBar() *Bar {
ib := new(Bar)
ib.History = make(map[string][]string)
ib.Buffer = buffer.NewBufferFromString("", "infobar", buffer.BTScratch)
return ib
}
// Message sends a message to the user
func (i *Bar) Message(msg ...interface{}) {
// 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 {
displayMessage := fmt.Sprint(msg...)
// if there is no active prompt then style and display the message as normal
i.Msg = displayMessage
i.HasMessage = true
}
}
// Error sends an error message to the user
func (i *Bar) Error(msg ...interface{}) {
// 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.Msg = fmt.Sprint(msg...)
i.HasError = true
}
// TODO: add to log?
}

View File

@@ -12,6 +12,8 @@ import (
"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/display"
"github.com/zyedidia/micro/cmd/micro/info"
"github.com/zyedidia/micro/cmd/micro/screen"
"github.com/zyedidia/micro/cmd/micro/util"
"github.com/zyedidia/tcell"
@@ -126,7 +128,7 @@ func LoadInput() []*buffer.Buffer {
continue
}
buf, err := buffer.NewBufferFromFile(args[i])
buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
if err != nil {
util.TermMessage(err)
continue
@@ -143,10 +145,10 @@ func LoadInput() []*buffer.Buffer {
util.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename))
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename))
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
}
return buffers
@@ -168,6 +170,7 @@ func main() {
}
config.InitGlobalSettings()
action.InitBindings()
err = config.InitColorscheme()
if err != nil {
util.TermMessage(err)
@@ -192,6 +195,10 @@ func main() {
width, height := screen.Screen.Size()
ep := NewBufEditPane(0, 0, width, height-1, b)
info.InitInfoBar()
infowindow := display.NewInfoWindow(info.MainBar)
infobar := action.NewBufHandler(info.MainBar.Buffer, infowindow)
// Here is the event loop which runs in a separate thread
go func() {
events = make(chan tcell.Event)
@@ -206,6 +213,7 @@ func main() {
// Display everything
screen.Screen.Fill(' ', config.DefStyle)
ep.Display()
infowindow.Display()
screen.Screen.Show()
var event tcell.Event
@@ -216,7 +224,11 @@ func main() {
}
if event != nil {
ep.HandleEvent(event)
if info.MainBar.HasPrompt {
infobar.HandleEvent(event)
} else {
ep.HandleEvent(event)
}
}
}

View File

@@ -18,12 +18,12 @@ func GetMemStats() string {
var start time.Time
func tic(s string) {
func Tic(s string) {
log.Println("START:", s)
start = time.Now()
}
func toc() {
func Toc() {
end := time.Now()
log.Println("END: ElapsedTime in seconds:", end.Sub(start))
}