package main
import (
+ "fmt"
"os"
+ "reflect"
"strconv"
"strings"
"time"
- "github.com/mitchellh/go-homedir"
"github.com/zyedidia/tcell"
)
+// The ViewType defines what kind of view this is
type ViewType struct {
- kind int
- readonly bool // The file cannot be edited
- scratch bool // The file cannot be saved
+ Kind int
+ Readonly bool // The file cannot be edited
+ Scratch bool // The file cannot be saved
}
var (
vtHelp = ViewType{1, true, true}
vtLog = ViewType{2, true, true}
vtScratch = ViewType{3, false, true}
+ vtRaw = ViewType{4, true, true}
)
// The View struct stores information about a view into a buffer.
// The buffer
Buf *Buffer
// The statusline
- sline Statusline
+ sline *Statusline
// Since tcell doesn't differentiate between a mouse release event
// and a mouse move event with no keys pressed, we need to keep
// mouse release events
mouseReleased bool
+ // We need to keep track of insert key press toggle
+ isOverwriteMode bool
// This stores when the last click was
// This is useful for detecting double and triple clicks
lastClickTime time.Time
+ lastLoc Loc
// lastCutTime stores when the last ctrl+k was issued.
// It is used for clearing the clipboard to replace it with fresh cut lines.
cellview *CellView
splitNode *LeafNode
+
+ scrollbar *ScrollBar
}
// NewView returns a new fullscreen view
v.messages = make(map[string][]GutterMessage)
- v.sline = Statusline{
+ v.sline = &Statusline{
+ view: v,
+ }
+
+ v.scrollbar = &ScrollBar{
view: v,
}
// If there are unsaved changes, the user will be asked if the view can be closed
// causing them to lose the unsaved changes
func (v *View) CanClose() bool {
- if v.Type == vtDefault && v.Buf.IsModified {
+ if v.Type == vtDefault && v.Buf.Modified() {
var choice bool
var canceled bool
if v.Buf.Settings["autosave"].(bool) {
//if char == 'y' {
if choice {
v.Save(true)
- return true
- } else {
- return true
}
+ } else {
+ return false
}
- } else {
- return true
}
- return false
+ return true
}
// OpenBuffer opens a new buffer in this view.
// Set mouseReleased to true because we assume the mouse is not being pressed when
// the editor is opened
v.mouseReleased = true
+ // Set isOverwriteMode to false, because we assume we are in the default mode when editor
+ // is opened
+ v.isOverwriteMode = false
v.lastClickTime = time.Time{}
}
// Open opens the given file in the view
func (v *View) Open(filename string) {
- home, _ := homedir.Dir()
- filename = strings.Replace(filename, "~", home, 1)
+ filename = ReplaceHome(filename)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
// HSplit opens a horizontal split with the given buffer
func (v *View) HSplit(buf *Buffer) {
i := 0
- if v.Buf.Settings["splitBottom"].(bool) {
+ if v.Buf.Settings["splitbottom"].(bool) {
i = 1
}
v.splitNode.HSplit(buf, v.Num+i)
// VSplit opens a vertical split with the given buffer
func (v *View) VSplit(buf *Buffer) {
i := 0
- if v.Buf.Settings["splitRight"].(bool) {
+ if v.Buf.Settings["splitright"].(bool) {
i = 1
}
v.splitNode.VSplit(buf, v.Num+i)
v.Cursor.LastVisualX = v.Cursor.GetVisualX()
}
+// Execute actions executes the supplied actions
func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
relocate := false
readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
for _, action := range actions {
readonlyBindingsResult := false
funcName := ShortFuncName(action)
- if v.Type.readonly == true {
+ if v.Type.Readonly == true {
// check for readonly and if true only let key bindings get called if they do not change the contents.
for _, readonlyBindings := range readonlyBindingsList {
if strings.Contains(funcName, readonlyBindings) {
return relocate
}
-func (v *View) SetCursor(c *Cursor) {
+// SetCursor sets the view's and buffer's cursor
+func (v *View) SetCursor(c *Cursor) bool {
+ if c == nil {
+ return false
+ }
v.Cursor = c
v.Buf.curCursor = c.Num
+
+ return true
}
// HandleEvent handles an event passed by the main loop
func (v *View) HandleEvent(event tcell.Event) {
+ if v.Type == vtRaw {
+ v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
+ v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
+
+ switch e := event.(type) {
+ case *tcell.EventKey:
+ if e.Key() == tcell.KeyCtrlQ {
+ v.Quit(true)
+ }
+ }
+
+ return
+ }
+
// This bool determines whether the view is relocated at the end of the function
// By default it's true because most events should cause a relocate
relocate := true
v.Buf.CheckModTime()
switch e := event.(type) {
+ case *tcell.EventRaw:
+ for key, actions := range bindings {
+ if key.keyCode == -1 {
+ if e.EscSeq() == key.escape {
+ for _, c := range v.Buf.cursors {
+ ok := v.SetCursor(c)
+ if !ok {
+ break
+ }
+ relocate = false
+ relocate = v.ExecuteActions(actions) || relocate
+ }
+ v.SetCursor(&v.Buf.Cursor)
+ v.Buf.MergeCursors()
+ break
+ }
+ }
+ }
case *tcell.EventKey:
// Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
isBinding := false
}
if e.Modifiers() == key.modifiers {
for _, c := range v.Buf.cursors {
- v.SetCursor(c)
+ ok := v.SetCursor(c)
+ if !ok {
+ break
+ }
relocate = false
isBinding = true
relocate = v.ExecuteActions(actions) || relocate
}
}
}
+
if !isBinding && e.Key() == tcell.KeyRune {
// Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
- if v.Type.readonly == false {
+ if v.Type.Readonly == false {
for _, c := range v.Buf.cursors {
v.SetCursor(c)
v.Cursor.DeleteSelection()
v.Cursor.ResetSelection()
}
- v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
+
+ if v.isOverwriteMode {
+ next := v.Cursor.Loc
+ next.X++
+ v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
+ } else {
+ v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
+ }
for pl := range loadedPlugins {
_, err := Call(pl+".onRune", string(e.Rune()), v)
}
case *tcell.EventPaste:
// Check viewtype if readonly don't paste (readonly help and log view etc.)
- if v.Type.readonly == false {
+ if v.Type.Readonly == false {
if !PreActionCall("Paste", v) {
break
}
for _, c := range v.Buf.cursors {
v.SetCursor(c)
v.paste(e.Text())
-
}
v.SetCursor(&v.Buf.Cursor)
for key, actions := range bindings {
if button == key.buttons && e.Modifiers() == key.modifiers {
for _, c := range v.Buf.cursors {
- v.SetCursor(c)
+ ok := v.SetCursor(c)
+ if !ok {
+ break
+ }
relocate = v.ExecuteActions(actions) || relocate
}
v.SetCursor(&v.Buf.Cursor)
}
}
+// DisplayView draws the view to the screen
func (v *View) DisplayView() {
if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
v.leftCol = 0
}
- if v.Type == vtLog {
- // Log views should always follow the cursor...
+ if v.Type == vtLog || v.Type == vtRaw {
+ // Log or raw views should always follow the cursor...
v.Relocate()
}
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
!v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
- for i := lastX; i < xOffset+v.Width; i++ {
+ for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
style := GetColor("cursor-line")
fg, _, _ := style.Decompose()
style = style.Background(fg)
screen.HideCursor()
}
_, screenH := screen.Size()
+
+ if v.Buf.Settings["scrollbar"].(bool) {
+ v.scrollbar.Display()
+ }
+
if v.Buf.Settings["statusline"].(bool) {
v.sline.Display()
} else if (v.y + v.Height) != screenH-1 {