From c50e0cb932d2f783c27865bf059a0eb985e16494 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Mon, 31 Dec 2018 22:07:01 -0500 Subject: [PATCH] Add infobar --- cmd/micro/action/actions.go | 55 +++++++++++++-------- cmd/micro/action/bufhandler.go | 4 +- cmd/micro/buffer/buffer.go | 27 ++++++----- cmd/micro/display/infobar.go | 84 --------------------------------- cmd/micro/display/infowindow.go | 74 +++++++++++++++++++++++++++++ cmd/micro/display/window.go | 31 +++++++----- cmd/micro/info/gutter.go | 18 +++++++ cmd/micro/info/history.go | 61 ++++++++++++++++++++++++ cmd/micro/info/infobar.go | 66 ++++++++++++++++++++++++++ cmd/micro/micro.go | 20 ++++++-- cmd/micro/util/profile.go | 4 +- 11 files changed, 309 insertions(+), 135 deletions(-) delete mode 100644 cmd/micro/display/infobar.go create mode 100644 cmd/micro/display/infowindow.go create mode 100644 cmd/micro/info/gutter.go create mode 100644 cmd/micro/info/history.go create mode 100644 cmd/micro/info/infobar.go diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index a9ea8e87..813831db 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -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 } diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index 80d490fa..3f2909fa 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -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 diff --git a/cmd/micro/buffer/buffer.go b/cmd/micro/buffer/buffer.go index e24bd18d..f6214429 100644 --- a/cmd/micro/buffer/buffer.go +++ b/cmd/micro/buffer/buffer.go @@ -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) { diff --git a/cmd/micro/display/infobar.go b/cmd/micro/display/infobar.go deleted file mode 100644 index 0c5be76e..00000000 --- a/cmd/micro/display/infobar.go +++ /dev/null @@ -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 - } -} diff --git a/cmd/micro/display/infowindow.go b/cmd/micro/display/infowindow.go new file mode 100644 index 00000000..278c5b74 --- /dev/null +++ b/cmd/micro/display/infowindow.go @@ -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) + } + } +} diff --git a/cmd/micro/display/window.go b/cmd/micro/display/window.go index d4967b5e..14aff6ad 100644 --- a/cmd/micro/display/window.go +++ b/cmd/micro/display/window.go @@ -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++ { diff --git a/cmd/micro/info/gutter.go b/cmd/micro/info/gutter.go new file mode 100644 index 00000000..5d95ccd0 --- /dev/null +++ b/cmd/micro/info/gutter.go @@ -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 +) diff --git a/cmd/micro/info/history.go b/cmd/micro/info/history.go new file mode 100644 index 00000000..663fd594 --- /dev/null +++ b/cmd/micro/info/history.go @@ -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 + } + } + } +} diff --git a/cmd/micro/info/infobar.go b/cmd/micro/info/infobar.go new file mode 100644 index 00000000..099f4571 --- /dev/null +++ b/cmd/micro/info/infobar.go @@ -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? +} diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 86374bac..c56b7506 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -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) + } } } diff --git a/cmd/micro/util/profile.go b/cmd/micro/util/profile.go index 61726e5c..d15655dd 100644 --- a/cmd/micro/util/profile.go +++ b/cmd/micro/util/profile.go @@ -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)) }