package main
import (
- "os"
+ "fmt"
+ "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}
+ vtTerm = ViewType{5, 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.
// Same here, just to keep track for mouse move events
tripleClick bool
+ // The cellview used for displaying and syntax highlighting
cellview *CellView
splitNode *LeafNode
+
+ // The scrollbar
+ scrollbar *ScrollBar
+
+ // Virtual terminal
+ term *Terminal
}
// 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,
}
v.Height--
}
+ v.term = new(Terminal)
+
for pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
}
}
+// StartTerminal execs a command in this view
+func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error {
+ err := v.term.Start(execCmd, v, getOutput)
+ v.term.wait = wait
+ v.term.callback = luaCallback
+ if err == nil {
+ v.term.Resize(v.Width, v.Height)
+ v.Type = vtTerm
+ }
+ return err
+}
+
+// CloseTerminal shuts down the tty running in this view
+// and returns it to the default view type
+func (v *View) CloseTerminal() {
+ v.term.Stop()
+}
+
// ToggleTabbar creates an extra row for the tabbar if necessary
func (v *View) ToggleTabbar() {
if len(tabs) > 1 {
}
func (v *View) paste(clip string) {
- leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
+ leadingWS := ""
+ if v.Cursor.X > 0 {
+ leadingWS = GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
+ }
if v.Cursor.HasSelection() {
v.Cursor.DeleteSelection()
// 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{}
+
+ GlobalPluginCall("onBufferOpen", v.Buf)
+ GlobalPluginCall("onViewOpen", v)
}
// Open opens the given file in the view
-func (v *View) Open(filename string) {
- home, _ := homedir.Dir()
- filename = strings.Replace(filename, "~", home, 1)
- file, err := os.Open(filename)
- fileInfo, _ := os.Stat(filename)
-
- if err == nil && fileInfo.IsDir() {
- messenger.Error(filename, " is a directory")
- return
- }
-
- defer file.Close()
-
- var buf *Buffer
+func (v *View) Open(path string) {
+ buf, err := NewBufferFromFile(path)
if err != nil {
- messenger.Message(err.Error())
- // File does not exist -- create an empty buffer with that name
- buf = NewBufferFromString("", filename)
- } else {
- buf = NewBuffer(file, FSize(file), filename)
+ messenger.Error(err)
+ return
}
v.OpenBuffer(buf)
}
// 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)
return 0, 0
}
+// Bottomline returns the line number of the lowest line in the view
+// You might think that this is obviously just v.Topline + v.Height
+// but if softwrap is enabled things get complicated since one buffer
+// line can take up multiple lines in the view
func (v *View) Bottomline() int {
if !v.Buf.Settings["softwrap"].(bool) {
return v.Topline + v.Height
if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
v.Topline = cy - height + 1 + scrollmargin
ret = true
- } else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
+ } else if cy >= v.Buf.NumLines-scrollmargin && cy >= height {
v.Topline = v.Buf.NumLines - height
ret = true
}
return ret
}
+// GetMouseClickLocation gets the location in the buffer from a mouse click
+// on the screen
+func (v *View) GetMouseClickLocation(x, y int) (int, int) {
+ x -= v.lineNumOffset - v.leftCol + v.x
+ y += v.Topline - v.y
+
+ if y-v.Topline > v.Height-1 {
+ v.ScrollDown(1)
+ y = v.Height + v.Topline - 1
+ }
+ if y < 0 {
+ y = 0
+ }
+ if x < 0 {
+ x = 0
+ }
+
+ newX, newY := v.GetSoftWrapLocation(x, y)
+ if newX > Count(v.Buf.Line(newY)) {
+ newX = Count(v.Buf.Line(newY))
+ }
+
+ return newX, newY
+}
+
// MoveToMouseClick moves the cursor to location x, y assuming x, y were given
// by a mouse click
func (v *View) MoveToMouseClick(x, y int) {
}
x, y = v.GetSoftWrapLocation(x, y)
- // x = v.Cursor.GetCharPosInLine(y, x)
if x > Count(v.Buf.Line(y)) {
x = Count(v.Buf.Line(y))
}
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 {
+ curv := CurView()
+ if curv.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) {
}
if !readonlyBindingsResult {
// call the key binding
- relocate = action(v, true) || relocate
+ relocate = action(curv, true) || relocate
// Macro
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
if recordingMacro {
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 == vtTerm {
+ v.term.HandleEvent(event)
+ return
+ }
+
+ 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.Type == vtTerm {
+ v.term.Display()
+ return
+ }
+
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()
}
}
colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
- if colorcolumn != 0 {
+ if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
style := GetColor("color-column")
fg, _, _ := style.Decompose()
st := defStyle.Background(fg)
- screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
+ screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
}
screenX = v.x
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 {