mirror of
https://github.com/zyedidia/micro
synced 2024-09-30 21:23:41 +00:00
commit
aeff0f8170
|
@ -2,10 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -296,242 +294,242 @@ func DefaultBindings() map[string]string {
|
|||
|
||||
// CursorUp moves the cursor up
|
||||
func (v *View) CursorUp() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.SetLoc(v.cursor.curSelection[0])
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.SetLoc(v.Cursor.curSelection[0])
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.cursor.Up()
|
||||
v.Cursor.Up()
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorDown moves the cursor down
|
||||
func (v *View) CursorDown() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.SetLoc(v.cursor.curSelection[1])
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.SetLoc(v.Cursor.curSelection[1])
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.cursor.Down()
|
||||
v.Cursor.Down()
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorLeft moves the cursor left
|
||||
func (v *View) CursorLeft() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.SetLoc(v.cursor.curSelection[0])
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.SetLoc(v.Cursor.curSelection[0])
|
||||
v.Cursor.ResetSelection()
|
||||
} else {
|
||||
v.cursor.Left()
|
||||
v.Cursor.Left()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorRight moves the cursor right
|
||||
func (v *View) CursorRight() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.SetLoc(v.cursor.curSelection[1] - 1)
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.SetLoc(v.Cursor.curSelection[1] - 1)
|
||||
v.Cursor.ResetSelection()
|
||||
} else {
|
||||
v.cursor.Right()
|
||||
v.Cursor.Right()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// WordRight moves the cursor one word to the right
|
||||
func (v *View) WordRight() bool {
|
||||
v.cursor.WordRight()
|
||||
v.Cursor.WordRight()
|
||||
return true
|
||||
}
|
||||
|
||||
// WordLeft moves the cursor one word to the left
|
||||
func (v *View) WordLeft() bool {
|
||||
v.cursor.WordLeft()
|
||||
v.Cursor.WordLeft()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectUp selects up one line
|
||||
func (v *View) SelectUp() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.Up()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.Up()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectDown selects down one line
|
||||
func (v *View) SelectDown() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.Down()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.Down()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectLeft selects the character to the left of the cursor
|
||||
func (v *View) SelectLeft() bool {
|
||||
loc := v.cursor.Loc()
|
||||
count := v.buf.Len() - 1
|
||||
loc := v.Cursor.Loc()
|
||||
count := v.Buf.Len() - 1
|
||||
if loc > count {
|
||||
loc = count
|
||||
}
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.Left()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.Left()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectRight selects the character to the right of the cursor
|
||||
func (v *View) SelectRight() bool {
|
||||
loc := v.cursor.Loc()
|
||||
count := v.buf.Len() - 1
|
||||
loc := v.Cursor.Loc()
|
||||
count := v.Buf.Len() - 1
|
||||
if loc > count {
|
||||
loc = count
|
||||
}
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.Right()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.Right()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectWordRight selects the word to the right of the cursor
|
||||
func (v *View) SelectWordRight() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.WordRight()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.WordRight()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectWordLeft selects the word to the left of the cursor
|
||||
func (v *View) SelectWordLeft() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.WordLeft()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.WordLeft()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfLine moves the cursor to the start of the line
|
||||
func (v *View) StartOfLine() bool {
|
||||
v.cursor.Start()
|
||||
v.Cursor.Start()
|
||||
return true
|
||||
}
|
||||
|
||||
// EndOfLine moves the cursor to the end of the line
|
||||
func (v *View) EndOfLine() bool {
|
||||
v.cursor.End()
|
||||
v.Cursor.End()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfLine selects to the start of the current line
|
||||
func (v *View) SelectToStartOfLine() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.Start()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.Start()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToEndOfLine selects to the end of the current line
|
||||
func (v *View) SelectToEndOfLine() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.cursor.End()
|
||||
v.cursor.SelectTo(v.cursor.Loc())
|
||||
v.Cursor.End()
|
||||
v.Cursor.SelectTo(v.Cursor.Loc())
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorStart moves the cursor to the start of the buffer
|
||||
func (v *View) CursorStart() bool {
|
||||
v.cursor.x = 0
|
||||
v.cursor.y = 0
|
||||
v.Cursor.x = 0
|
||||
v.Cursor.y = 0
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorEnd moves the cursor to the end of the buffer
|
||||
func (v *View) CursorEnd() bool {
|
||||
v.cursor.SetLoc(v.buf.Len())
|
||||
v.Cursor.SetLoc(v.Buf.Len())
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStart selects the text from the cursor to the start of the buffer
|
||||
func (v *View) SelectToStart() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.CursorStart()
|
||||
v.cursor.SelectTo(0)
|
||||
v.Cursor.SelectTo(0)
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToEnd selects the text from the cursor to the end of the buffer
|
||||
func (v *View) SelectToEnd() bool {
|
||||
loc := v.cursor.Loc()
|
||||
if !v.cursor.HasSelection() {
|
||||
v.cursor.origSelection[0] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.origSelection[0] = loc
|
||||
}
|
||||
v.CursorEnd()
|
||||
v.cursor.SelectTo(v.buf.Len())
|
||||
v.Cursor.SelectTo(v.Buf.Len())
|
||||
return true
|
||||
}
|
||||
|
||||
// InsertSpace inserts a space
|
||||
func (v *View) InsertSpace() bool {
|
||||
// Insert a space
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.eh.Insert(v.cursor.Loc(), " ")
|
||||
v.cursor.Right()
|
||||
v.eh.Insert(v.Cursor.Loc(), " ")
|
||||
v.Cursor.Right()
|
||||
return true
|
||||
}
|
||||
|
||||
// InsertEnter inserts a newline plus possible some whitespace if autoindent is on
|
||||
func (v *View) InsertEnter() bool {
|
||||
// Insert a newline
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
|
||||
v.eh.Insert(v.cursor.Loc(), "\n")
|
||||
ws := GetLeadingWhitespace(v.buf.lines[v.cursor.y])
|
||||
v.cursor.Right()
|
||||
v.eh.Insert(v.Cursor.Loc(), "\n")
|
||||
ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.y])
|
||||
v.Cursor.Right()
|
||||
|
||||
if settings["autoindent"].(bool) {
|
||||
v.eh.Insert(v.cursor.Loc(), ws)
|
||||
v.eh.Insert(v.Cursor.Loc(), ws)
|
||||
for i := 0; i < len(ws); i++ {
|
||||
v.cursor.Right()
|
||||
v.Cursor.Right()
|
||||
}
|
||||
}
|
||||
v.cursor.lastVisualX = v.cursor.GetVisualX()
|
||||
v.Cursor.lastVisualX = v.Cursor.GetVisualX()
|
||||
return true
|
||||
}
|
||||
|
||||
// Backspace deletes the previous character
|
||||
func (v *View) Backspace() bool {
|
||||
// Delete a character
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
} else if v.cursor.Loc() > 0 {
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
} else if v.Cursor.Loc() > 0 {
|
||||
// We have to do something a bit hacky here because we want to
|
||||
// delete the line by first moving left and then deleting backwards
|
||||
// but the undo redo would place the cursor in the wrong place
|
||||
|
@ -541,36 +539,36 @@ func (v *View) Backspace() bool {
|
|||
// If the user is using spaces instead of tabs and they are deleting
|
||||
// whitespace at the start of the line, we should delete as if its a
|
||||
// tab (tabSize number of spaces)
|
||||
lineStart := v.buf.lines[v.cursor.y][:v.cursor.x]
|
||||
lineStart := v.Buf.Lines[v.Cursor.y][:v.Cursor.x]
|
||||
tabSize := int(settings["tabsize"].(float64))
|
||||
if settings["tabsToSpaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
|
||||
loc := v.cursor.Loc()
|
||||
v.cursor.SetLoc(loc - tabSize)
|
||||
cx, cy := v.cursor.x, v.cursor.y
|
||||
v.cursor.SetLoc(loc)
|
||||
loc := v.Cursor.Loc()
|
||||
v.Cursor.SetLoc(loc - tabSize)
|
||||
cx, cy := v.Cursor.x, v.Cursor.y
|
||||
v.Cursor.SetLoc(loc)
|
||||
v.eh.Remove(loc-tabSize, loc)
|
||||
v.cursor.x, v.cursor.y = cx, cy
|
||||
v.Cursor.x, v.Cursor.y = cx, cy
|
||||
} else {
|
||||
v.cursor.Left()
|
||||
cx, cy := v.cursor.x, v.cursor.y
|
||||
v.cursor.Right()
|
||||
loc := v.cursor.Loc()
|
||||
v.Cursor.Left()
|
||||
cx, cy := v.Cursor.x, v.Cursor.y
|
||||
v.Cursor.Right()
|
||||
loc := v.Cursor.Loc()
|
||||
v.eh.Remove(loc-1, loc)
|
||||
v.cursor.x, v.cursor.y = cx, cy
|
||||
v.Cursor.x, v.Cursor.y = cx, cy
|
||||
}
|
||||
}
|
||||
v.cursor.lastVisualX = v.cursor.GetVisualX()
|
||||
v.Cursor.lastVisualX = v.Cursor.GetVisualX()
|
||||
return true
|
||||
}
|
||||
|
||||
// Delete deletes the next character
|
||||
func (v *View) Delete() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
} else {
|
||||
loc := v.cursor.Loc()
|
||||
if loc < v.buf.Len() {
|
||||
loc := v.Cursor.Loc()
|
||||
if loc < v.Buf.Len() {
|
||||
v.eh.Remove(loc, loc+1)
|
||||
}
|
||||
}
|
||||
|
@ -580,19 +578,19 @@ func (v *View) Delete() bool {
|
|||
// InsertTab inserts a tab or spaces
|
||||
func (v *View) InsertTab() bool {
|
||||
// Insert a tab
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
if settings["tabsToSpaces"].(bool) {
|
||||
tabSize := int(settings["tabsize"].(float64))
|
||||
v.eh.Insert(v.cursor.Loc(), Spaces(tabSize))
|
||||
v.eh.Insert(v.Cursor.Loc(), Spaces(tabSize))
|
||||
for i := 0; i < tabSize; i++ {
|
||||
v.cursor.Right()
|
||||
v.Cursor.Right()
|
||||
}
|
||||
} else {
|
||||
v.eh.Insert(v.cursor.Loc(), "\t")
|
||||
v.cursor.Right()
|
||||
v.eh.Insert(v.Cursor.Loc(), "\t")
|
||||
v.Cursor.Right()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -600,61 +598,30 @@ func (v *View) InsertTab() bool {
|
|||
// Save the buffer to disk
|
||||
func (v *View) Save() bool {
|
||||
// If this is an empty buffer, ask for a filename
|
||||
if v.buf.path == "" {
|
||||
if v.Buf.Path == "" {
|
||||
filename, canceled := messenger.Prompt("Filename: ")
|
||||
if !canceled {
|
||||
v.buf.path = filename
|
||||
v.buf.name = filename
|
||||
v.Buf.Path = filename
|
||||
v.Buf.Name = filename
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
err := v.buf.Save()
|
||||
err := v.Buf.Save()
|
||||
if err != nil {
|
||||
messenger.Error(err.Error())
|
||||
} else {
|
||||
messenger.Message("Saved " + v.buf.path)
|
||||
switch v.buf.filetype {
|
||||
case "Go":
|
||||
v.GoSave()
|
||||
}
|
||||
messenger.Message("Saved " + v.Buf.Path)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GoSave saves the current file (must be a go file) and runs goimports or gofmt
|
||||
// depending on the user's configuration
|
||||
func (v *View) GoSave() {
|
||||
if settings["goimports"] == true {
|
||||
messenger.Message("Running goimports...")
|
||||
err := goimports(v.buf.path)
|
||||
if err != nil {
|
||||
messenger.Error(err)
|
||||
} else {
|
||||
messenger.Message("Saved " + v.buf.path)
|
||||
}
|
||||
v.reOpen()
|
||||
} else if settings["gofmt"] == true {
|
||||
messenger.Message("Running gofmt...")
|
||||
err := gofmt(v.buf.path)
|
||||
if err != nil {
|
||||
messenger.Error(err)
|
||||
} else {
|
||||
messenger.Message("Saved " + v.buf.path)
|
||||
}
|
||||
v.reOpen()
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Find opens a prompt and searches forward for the input
|
||||
func (v *View) Find() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
searchStart = v.cursor.curSelection[1]
|
||||
if v.Cursor.HasSelection() {
|
||||
searchStart = v.Cursor.curSelection[1]
|
||||
} else {
|
||||
searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
|
||||
searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
|
||||
}
|
||||
BeginSearch()
|
||||
return true
|
||||
|
@ -662,10 +629,10 @@ func (v *View) Find() bool {
|
|||
|
||||
// FindNext searches forwards for the last used search term
|
||||
func (v *View) FindNext() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
searchStart = v.cursor.curSelection[1]
|
||||
if v.Cursor.HasSelection() {
|
||||
searchStart = v.Cursor.curSelection[1]
|
||||
} else {
|
||||
searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
|
||||
searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
|
||||
}
|
||||
messenger.Message("Finding: " + lastSearch)
|
||||
Search(lastSearch, v, true)
|
||||
|
@ -674,10 +641,10 @@ func (v *View) FindNext() bool {
|
|||
|
||||
// FindPrevious searches backwards for the last used search term
|
||||
func (v *View) FindPrevious() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
searchStart = v.cursor.curSelection[0]
|
||||
if v.Cursor.HasSelection() {
|
||||
searchStart = v.Cursor.curSelection[0]
|
||||
} else {
|
||||
searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
|
||||
searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
|
||||
}
|
||||
messenger.Message("Finding: " + lastSearch)
|
||||
Search(lastSearch, v, false)
|
||||
|
@ -698,8 +665,8 @@ func (v *View) Redo() bool {
|
|||
|
||||
// Copy the selection to the system clipboard
|
||||
func (v *View) Copy() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
clipboard.WriteAll(v.cursor.GetSelection())
|
||||
if v.Cursor.HasSelection() {
|
||||
clipboard.WriteAll(v.Cursor.GetSelection())
|
||||
v.freshClip = true
|
||||
}
|
||||
return true
|
||||
|
@ -707,14 +674,14 @@ func (v *View) Copy() bool {
|
|||
|
||||
// CutLine cuts the current line to the clipboard
|
||||
func (v *View) CutLine() bool {
|
||||
v.cursor.SelectLine()
|
||||
v.Cursor.SelectLine()
|
||||
if v.freshClip == true {
|
||||
|
||||
if v.cursor.HasSelection() {
|
||||
if v.Cursor.HasSelection() {
|
||||
if clip, err := clipboard.ReadAll(); err != nil {
|
||||
messenger.Error(err)
|
||||
} else {
|
||||
clipboard.WriteAll(clip + v.cursor.GetSelection())
|
||||
clipboard.WriteAll(clip + v.Cursor.GetSelection())
|
||||
}
|
||||
}
|
||||
} else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
|
||||
|
@ -722,17 +689,17 @@ func (v *View) CutLine() bool {
|
|||
}
|
||||
v.freshClip = true
|
||||
v.lastCutTime = time.Now()
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
return true
|
||||
}
|
||||
|
||||
// Cut the selection to the system clipboard
|
||||
func (v *View) Cut() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
clipboard.WriteAll(v.cursor.GetSelection())
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
clipboard.WriteAll(v.Cursor.GetSelection())
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
v.freshClip = true
|
||||
}
|
||||
return true
|
||||
|
@ -741,24 +708,24 @@ func (v *View) Cut() bool {
|
|||
// Paste whatever is in the system clipboard into the buffer
|
||||
// Delete and paste if the user has a selection
|
||||
func (v *View) Paste() bool {
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
clip, _ := clipboard.ReadAll()
|
||||
v.eh.Insert(v.cursor.Loc(), clip)
|
||||
v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
|
||||
v.eh.Insert(v.Cursor.Loc(), clip)
|
||||
v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
|
||||
v.freshClip = false
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectAll selects the entire buffer
|
||||
func (v *View) SelectAll() bool {
|
||||
v.cursor.curSelection[1] = 0
|
||||
v.cursor.curSelection[0] = v.buf.Len()
|
||||
v.Cursor.curSelection[1] = 0
|
||||
v.Cursor.curSelection[0] = v.Buf.Len()
|
||||
// Put the cursor at the beginning
|
||||
v.cursor.x = 0
|
||||
v.cursor.y = 0
|
||||
v.Cursor.x = 0
|
||||
v.Cursor.y = 0
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -785,57 +752,57 @@ func (v *View) OpenFile() bool {
|
|||
|
||||
// Start moves the viewport to the start of the buffer
|
||||
func (v *View) Start() bool {
|
||||
v.topline = 0
|
||||
v.Topline = 0
|
||||
return false
|
||||
}
|
||||
|
||||
// End moves the viewport to the end of the buffer
|
||||
func (v *View) End() bool {
|
||||
if v.height > v.buf.numLines {
|
||||
v.topline = 0
|
||||
if v.height > v.Buf.NumLines {
|
||||
v.Topline = 0
|
||||
} else {
|
||||
v.topline = v.buf.numLines - v.height
|
||||
v.Topline = v.Buf.NumLines - v.height
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PageUp scrolls the view up a page
|
||||
func (v *View) PageUp() bool {
|
||||
if v.topline > v.height {
|
||||
if v.Topline > v.height {
|
||||
v.ScrollUp(v.height)
|
||||
} else {
|
||||
v.topline = 0
|
||||
v.Topline = 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PageDown scrolls the view down a page
|
||||
func (v *View) PageDown() bool {
|
||||
if v.buf.numLines-(v.topline+v.height) > v.height {
|
||||
if v.Buf.NumLines-(v.Topline+v.height) > v.height {
|
||||
v.ScrollDown(v.height)
|
||||
} else if v.buf.numLines >= v.height {
|
||||
v.topline = v.buf.numLines - v.height
|
||||
} else if v.Buf.NumLines >= v.height {
|
||||
v.Topline = v.Buf.NumLines - v.height
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HalfPageUp scrolls the view up half a page
|
||||
func (v *View) HalfPageUp() bool {
|
||||
if v.topline > v.height/2 {
|
||||
if v.Topline > v.height/2 {
|
||||
v.ScrollUp(v.height / 2)
|
||||
} else {
|
||||
v.topline = 0
|
||||
v.Topline = 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HalfPageDown scrolls the view down half a page
|
||||
func (v *View) HalfPageDown() bool {
|
||||
if v.buf.numLines-(v.topline+v.height) > v.height/2 {
|
||||
if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
|
||||
v.ScrollDown(v.height / 2)
|
||||
} else {
|
||||
if v.buf.numLines >= v.height {
|
||||
v.topline = v.buf.numLines - v.height
|
||||
if v.Buf.NumLines >= v.height {
|
||||
v.Topline = v.Buf.NumLines - v.height
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
@ -865,12 +832,12 @@ func (v *View) JumpLine() bool {
|
|||
return false
|
||||
}
|
||||
// Move cursor and view if possible.
|
||||
if lineint < v.buf.numLines {
|
||||
v.cursor.x = 0
|
||||
v.cursor.y = lineint
|
||||
if lineint < v.Buf.NumLines {
|
||||
v.Cursor.x = 0
|
||||
v.Cursor.y = lineint
|
||||
return true
|
||||
}
|
||||
messenger.Error("Only ", v.buf.numLines, " lines to jump")
|
||||
messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -878,25 +845,3 @@ func (v *View) JumpLine() bool {
|
|||
func None() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// gofmt runs gofmt on a file
|
||||
func gofmt(file string) error {
|
||||
cmd := exec.Command("gofmt", "-w", file)
|
||||
cmd.Start()
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
return errors.New("Check syntax ") //TODO: highlight or display locations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// goimports runs goimports on a file
|
||||
func goimports(file string) error {
|
||||
cmd := exec.Command("goimports", "-w", file)
|
||||
cmd.Start()
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
return errors.New("Check syntax ") //TODO: highlight or display locations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ type Buffer struct {
|
|||
r *rope.Rope
|
||||
|
||||
// Path to the file on disk
|
||||
path string
|
||||
Path string
|
||||
// Name of the buffer on the status line
|
||||
name string
|
||||
Name string
|
||||
|
||||
// This is the text stored every time the buffer is saved to check if the buffer is modified
|
||||
savedText [16]byte
|
||||
|
@ -27,13 +27,13 @@ type Buffer struct {
|
|||
// Provide efficient and easy access to text and lines so the rope String does not
|
||||
// need to be constantly recalculated
|
||||
// These variables are updated in the update() function
|
||||
lines []string
|
||||
numLines int
|
||||
Lines []string
|
||||
NumLines int
|
||||
|
||||
// Syntax highlighting rules
|
||||
rules []SyntaxRule
|
||||
// The buffer's filetype
|
||||
filetype string
|
||||
Filetype string
|
||||
}
|
||||
|
||||
// NewBuffer creates a new buffer from `txt` with path and name `path`
|
||||
|
@ -44,8 +44,8 @@ func NewBuffer(txt, path string) *Buffer {
|
|||
} else {
|
||||
b.r = rope.New(txt)
|
||||
}
|
||||
b.path = path
|
||||
b.name = path
|
||||
b.Path = path
|
||||
b.Name = path
|
||||
b.savedText = md5.Sum([]byte(txt))
|
||||
|
||||
b.Update()
|
||||
|
@ -57,7 +57,7 @@ func NewBuffer(txt, path string) *Buffer {
|
|||
// UpdateRules updates the syntax rules and filetype for this buffer
|
||||
// This is called when the colorscheme changes
|
||||
func (b *Buffer) UpdateRules() {
|
||||
b.rules, b.filetype = GetRules(b)
|
||||
b.rules, b.Filetype = GetRules(b)
|
||||
}
|
||||
|
||||
func (b *Buffer) String() string {
|
||||
|
@ -70,13 +70,13 @@ func (b *Buffer) String() string {
|
|||
|
||||
// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
|
||||
func (b *Buffer) Update() {
|
||||
b.lines = strings.Split(b.String(), "\n")
|
||||
b.numLines = len(b.lines)
|
||||
b.Lines = strings.Split(b.String(), "\n")
|
||||
b.NumLines = len(b.Lines)
|
||||
}
|
||||
|
||||
// Save saves the buffer to its default path
|
||||
func (b *Buffer) Save() error {
|
||||
return b.SaveAs(b.path)
|
||||
return b.SaveAs(b.Path)
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
|
||||
|
|
|
@ -141,7 +141,7 @@ func HandleCommand(input string, view *View) {
|
|||
|
||||
found := false
|
||||
for {
|
||||
match := regex.FindStringIndex(view.buf.String())
|
||||
match := regex.FindStringIndex(view.Buf.String())
|
||||
if match == nil {
|
||||
break
|
||||
}
|
||||
|
@ -153,23 +153,23 @@ func HandleCommand(input string, view *View) {
|
|||
Redraw(view)
|
||||
choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
|
||||
if canceled {
|
||||
if view.cursor.HasSelection() {
|
||||
view.cursor.SetLoc(view.cursor.curSelection[0])
|
||||
view.cursor.ResetSelection()
|
||||
if view.Cursor.HasSelection() {
|
||||
view.Cursor.SetLoc(view.Cursor.curSelection[0])
|
||||
view.Cursor.ResetSelection()
|
||||
}
|
||||
messenger.Reset()
|
||||
return
|
||||
}
|
||||
if choice {
|
||||
view.cursor.DeleteSelection()
|
||||
view.Cursor.DeleteSelection()
|
||||
view.eh.Insert(match[0], replace)
|
||||
view.cursor.ResetSelection()
|
||||
view.Cursor.ResetSelection()
|
||||
messenger.Reset()
|
||||
} else {
|
||||
if view.cursor.HasSelection() {
|
||||
searchStart = view.cursor.curSelection[1]
|
||||
if view.Cursor.HasSelection() {
|
||||
searchStart = view.Cursor.curSelection[1]
|
||||
} else {
|
||||
searchStart = ToCharPos(view.cursor.x, view.cursor.y, view.buf)
|
||||
searchStart = ToCharPos(view.Cursor.x, view.Cursor.y, view.Buf)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -14,11 +14,11 @@ func FromCharPosStart(startLoc, startX, startY, loc int, buf *Buffer) (int, int)
|
|||
charNum := startLoc
|
||||
x, y := startX, startY
|
||||
|
||||
lineLen := Count(buf.lines[y]) + 1
|
||||
lineLen := Count(buf.Lines[y]) + 1
|
||||
for charNum+lineLen <= loc {
|
||||
charNum += lineLen
|
||||
y++
|
||||
lineLen = Count(buf.lines[y]) + 1
|
||||
lineLen = Count(buf.Lines[y]) + 1
|
||||
}
|
||||
x = loc - charNum
|
||||
|
||||
|
@ -30,7 +30,7 @@ func ToCharPos(x, y int, buf *Buffer) int {
|
|||
loc := 0
|
||||
for i := 0; i < y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += Count(buf.lines[i]) + 1
|
||||
loc += Count(buf.Lines[i]) + 1
|
||||
}
|
||||
loc += x
|
||||
return loc
|
||||
|
@ -64,7 +64,7 @@ type Cursor struct {
|
|||
// and not x, y location
|
||||
// It's just a simple wrapper of FromCharPos
|
||||
func (c *Cursor) SetLoc(loc int) {
|
||||
c.x, c.y = FromCharPos(loc, c.v.buf)
|
||||
c.x, c.y = FromCharPos(loc, c.v.Buf)
|
||||
c.lastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ func (c *Cursor) SetLoc(loc int) {
|
|||
// of x, y location
|
||||
// It's just a simple wrapper of ToCharPos
|
||||
func (c *Cursor) Loc() int {
|
||||
return ToCharPos(c.x, c.y, c.v.buf)
|
||||
return ToCharPos(c.x, c.y, c.v.Buf)
|
||||
}
|
||||
|
||||
// ResetSelection resets the user's selection
|
||||
|
@ -102,9 +102,9 @@ func (c *Cursor) DeleteSelection() {
|
|||
// GetSelection returns the cursor's selection
|
||||
func (c *Cursor) GetSelection() string {
|
||||
if c.curSelection[0] > c.curSelection[1] {
|
||||
return c.v.buf.r.Report(c.curSelection[1]+1, c.curSelection[0]-c.curSelection[1])
|
||||
return c.v.Buf.r.Report(c.curSelection[1]+1, c.curSelection[0]-c.curSelection[1])
|
||||
}
|
||||
return c.v.buf.r.Report(c.curSelection[0]+1, c.curSelection[1]-c.curSelection[0])
|
||||
return c.v.Buf.r.Report(c.curSelection[0]+1, c.curSelection[1]-c.curSelection[0])
|
||||
}
|
||||
|
||||
// SelectLine selects the current line
|
||||
|
@ -112,7 +112,7 @@ func (c *Cursor) SelectLine() {
|
|||
c.Start()
|
||||
c.curSelection[0] = c.Loc()
|
||||
c.End()
|
||||
if c.v.buf.numLines-1 > c.y {
|
||||
if c.v.Buf.NumLines-1 > c.y {
|
||||
c.curSelection[1] = c.Loc() + 1
|
||||
} else {
|
||||
c.curSelection[1] = c.Loc()
|
||||
|
@ -143,7 +143,7 @@ func (c *Cursor) AddLineToSelection() {
|
|||
|
||||
// SelectWord selects the word the cursor is currently on
|
||||
func (c *Cursor) SelectWord() {
|
||||
if len(c.v.buf.lines[c.y]) == 0 {
|
||||
if len(c.v.Buf.Lines[c.y]) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -161,14 +161,14 @@ func (c *Cursor) SelectWord() {
|
|||
backward--
|
||||
}
|
||||
|
||||
c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf)
|
||||
c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf)
|
||||
c.origSelection[0] = c.curSelection[0]
|
||||
|
||||
for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
for forward < Count(c.v.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
|
||||
c.curSelection[1] = ToCharPos(forward, c.y, c.v.Buf) + 1
|
||||
c.origSelection[1] = c.curSelection[1]
|
||||
c.SetLoc(c.curSelection[1])
|
||||
}
|
||||
|
@ -189,18 +189,18 @@ func (c *Cursor) AddWordToSelection() {
|
|||
backward--
|
||||
}
|
||||
|
||||
c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf)
|
||||
c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf)
|
||||
c.curSelection[1] = c.origSelection[1]
|
||||
}
|
||||
|
||||
if loc > c.origSelection[1] {
|
||||
forward := c.x
|
||||
|
||||
for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
for forward < Count(c.v.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
|
||||
c.curSelection[1] = ToCharPos(forward, c.y, c.v.Buf) + 1
|
||||
c.curSelection[0] = c.origSelection[0]
|
||||
}
|
||||
|
||||
|
@ -222,13 +222,13 @@ func (c *Cursor) SelectTo(loc int) {
|
|||
func (c *Cursor) WordRight() {
|
||||
c.Right()
|
||||
for IsWhitespace(c.RuneUnder(c.x)) {
|
||||
if c.x == Count(c.v.buf.lines[c.y]) {
|
||||
if c.x == Count(c.v.Buf.Lines[c.y]) {
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
for !IsWhitespace(c.RuneUnder(c.x)) {
|
||||
if c.x == Count(c.v.buf.lines[c.y]) {
|
||||
if c.x == Count(c.v.Buf.Lines[c.y]) {
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
|
@ -255,7 +255,7 @@ func (c *Cursor) WordLeft() {
|
|||
|
||||
// RuneUnder returns the rune under the given x position
|
||||
func (c *Cursor) RuneUnder(x int) rune {
|
||||
line := []rune(c.v.buf.lines[c.y])
|
||||
line := []rune(c.v.Buf.Lines[c.y])
|
||||
if len(line) == 0 {
|
||||
return '\n'
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ func (c *Cursor) Up() {
|
|||
if c.y > 0 {
|
||||
c.y--
|
||||
|
||||
runes := []rune(c.v.buf.lines[c.y])
|
||||
runes := []rune(c.v.Buf.Lines[c.y])
|
||||
c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
|
||||
if c.x > len(runes) {
|
||||
c.x = len(runes)
|
||||
|
@ -282,10 +282,10 @@ func (c *Cursor) Up() {
|
|||
|
||||
// Down moves the cursor down one line (if possible)
|
||||
func (c *Cursor) Down() {
|
||||
if c.y < c.v.buf.numLines-1 {
|
||||
if c.y < c.v.Buf.NumLines-1 {
|
||||
c.y++
|
||||
|
||||
runes := []rune(c.v.buf.lines[c.y])
|
||||
runes := []rune(c.v.Buf.Lines[c.y])
|
||||
c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
|
||||
if c.x > len(runes) {
|
||||
c.x = len(runes)
|
||||
|
@ -309,10 +309,10 @@ func (c *Cursor) Left() {
|
|||
|
||||
// Right moves the cursor right one cell (if possible) or to the next line if it is at the end
|
||||
func (c *Cursor) Right() {
|
||||
if c.Loc() == c.v.buf.Len() {
|
||||
if c.Loc() == c.v.Buf.Len() {
|
||||
return
|
||||
}
|
||||
if c.x < Count(c.v.buf.lines[c.y]) {
|
||||
if c.x < Count(c.v.Buf.Lines[c.y]) {
|
||||
c.x++
|
||||
} else {
|
||||
c.Down()
|
||||
|
@ -323,7 +323,7 @@ func (c *Cursor) Right() {
|
|||
|
||||
// End moves the cursor to the end of the line it is on
|
||||
func (c *Cursor) End() {
|
||||
c.x = Count(c.v.buf.lines[c.y])
|
||||
c.x = Count(c.v.Buf.Lines[c.y])
|
||||
c.lastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
|
@ -338,7 +338,7 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
|
|||
// Get the tab size
|
||||
tabSize := int(settings["tabsize"].(float64))
|
||||
// This is the visual line -- every \t replaced with the correct number of spaces
|
||||
visualLine := strings.Replace(c.v.buf.lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
|
||||
visualLine := strings.Replace(c.v.Buf.Lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
|
||||
if visualPos > Count(visualLine) {
|
||||
visualPos = Count(visualLine)
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
|
|||
|
||||
// GetVisualX returns the x value of the cursor in visual spaces
|
||||
func (c *Cursor) GetVisualX() int {
|
||||
runes := []rune(c.v.buf.lines[c.y])
|
||||
runes := []rune(c.v.Buf.Lines[c.y])
|
||||
tabSize := int(settings["tabsize"].(float64))
|
||||
return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
|
||||
}
|
||||
|
@ -361,23 +361,23 @@ func (c *Cursor) GetVisualX() int {
|
|||
func (c *Cursor) Relocate() {
|
||||
if c.y < 0 {
|
||||
c.y = 0
|
||||
} else if c.y >= c.v.buf.numLines {
|
||||
c.y = c.v.buf.numLines - 1
|
||||
} else if c.y >= c.v.Buf.NumLines {
|
||||
c.y = c.v.Buf.NumLines - 1
|
||||
}
|
||||
|
||||
if c.x < 0 {
|
||||
c.x = 0
|
||||
} else if c.x > Count(c.v.buf.lines[c.y]) {
|
||||
c.x = Count(c.v.buf.lines[c.y])
|
||||
} else if c.x > Count(c.v.Buf.Lines[c.y]) {
|
||||
c.x = Count(c.v.Buf.Lines[c.y])
|
||||
}
|
||||
}
|
||||
|
||||
// Display draws the cursor to the screen at the correct position
|
||||
func (c *Cursor) Display() {
|
||||
// Don't draw the cursor if it is out of the viewport or if it has a selection
|
||||
if (c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.height-1) || c.HasSelection() {
|
||||
if (c.y-c.v.Topline < 0 || c.y-c.v.Topline > c.v.height-1) || c.HasSelection() {
|
||||
screen.HideCursor()
|
||||
} else {
|
||||
screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.topline)
|
||||
screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.Topline)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ func NewEventHandler(v *View) *EventHandler {
|
|||
// Insert creates an insert text event and executes it
|
||||
func (eh *EventHandler) Insert(start int, text string) {
|
||||
e := &TextEvent{
|
||||
c: eh.v.cursor,
|
||||
c: eh.v.Cursor,
|
||||
eventType: TextEventInsert,
|
||||
text: text,
|
||||
start: start,
|
||||
|
@ -71,7 +71,7 @@ func (eh *EventHandler) Insert(start int, text string) {
|
|||
// Remove creates a remove text event and executes it
|
||||
func (eh *EventHandler) Remove(start, end int) {
|
||||
e := &TextEvent{
|
||||
c: eh.v.cursor,
|
||||
c: eh.v.Cursor,
|
||||
eventType: TextEventRemove,
|
||||
start: start,
|
||||
end: end,
|
||||
|
@ -92,7 +92,7 @@ func (eh *EventHandler) Execute(t *TextEvent) {
|
|||
eh.redo = new(Stack)
|
||||
}
|
||||
eh.undo.Push(t)
|
||||
ExecuteTextEvent(t, eh.v.buf)
|
||||
ExecuteTextEvent(t, eh.v.Buf)
|
||||
}
|
||||
|
||||
// Undo the first event in the undo stack
|
||||
|
@ -135,12 +135,12 @@ func (eh *EventHandler) UndoOneEvent() {
|
|||
te := t.(*TextEvent)
|
||||
// Undo it
|
||||
// Modifies the text event
|
||||
UndoTextEvent(te, eh.v.buf)
|
||||
UndoTextEvent(te, eh.v.Buf)
|
||||
|
||||
// Set the cursor in the right place
|
||||
teCursor := te.c
|
||||
te.c = eh.v.cursor
|
||||
eh.v.cursor = teCursor
|
||||
te.c = eh.v.Cursor
|
||||
eh.v.Cursor = teCursor
|
||||
|
||||
// Push it to the redo stack
|
||||
eh.redo.Push(te)
|
||||
|
@ -183,11 +183,11 @@ func (eh *EventHandler) RedoOneEvent() {
|
|||
|
||||
te := t.(*TextEvent)
|
||||
// Modifies the text event
|
||||
UndoTextEvent(te, eh.v.buf)
|
||||
UndoTextEvent(te, eh.v.Buf)
|
||||
|
||||
teCursor := te.c
|
||||
te.c = eh.v.cursor
|
||||
eh.v.cursor = teCursor
|
||||
te.c = eh.v.Cursor
|
||||
eh.v.Cursor = teCursor
|
||||
|
||||
eh.undo.Push(te)
|
||||
}
|
||||
|
|
|
@ -363,10 +363,10 @@ func LoadRulesFromFile(text, filename string) []SyntaxRule {
|
|||
// and returns them. It also returns the filetype of the file
|
||||
func GetRules(buf *Buffer) ([]SyntaxRule, string) {
|
||||
for r := range syntaxFiles {
|
||||
if r[0] != nil && r[0].MatchString(buf.path) {
|
||||
if r[0] != nil && r[0].MatchString(buf.Path) {
|
||||
// Check if the syntax statement matches the extension
|
||||
return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype
|
||||
} else if r[1] != nil && r[1].MatchString(buf.lines[0]) {
|
||||
} else if r[1] != nil && r[1].MatchString(buf.Lines[0]) {
|
||||
// Check if the header statement matches the first line
|
||||
return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype
|
||||
}
|
||||
|
@ -381,16 +381,16 @@ type SyntaxMatches [][]tcell.Style
|
|||
// Match takes a buffer and returns the syntax matches: a 2d array specifying how it should be syntax highlighted
|
||||
// We match the rules from up `synLinesUp` lines and down `synLinesDown` lines
|
||||
func Match(v *View) SyntaxMatches {
|
||||
buf := v.buf
|
||||
rules := v.buf.rules
|
||||
buf := v.Buf
|
||||
rules := v.Buf.rules
|
||||
|
||||
viewStart := v.topline
|
||||
viewEnd := v.topline + v.height
|
||||
if viewEnd > buf.numLines {
|
||||
viewEnd = buf.numLines
|
||||
viewStart := v.Topline
|
||||
viewEnd := v.Topline + v.height
|
||||
if viewEnd > buf.NumLines {
|
||||
viewEnd = buf.NumLines
|
||||
}
|
||||
|
||||
lines := buf.lines[viewStart:viewEnd]
|
||||
lines := buf.Lines[viewStart:viewEnd]
|
||||
matches := make(SyntaxMatches, len(lines))
|
||||
|
||||
for i, line := range lines {
|
||||
|
@ -398,19 +398,19 @@ func Match(v *View) SyntaxMatches {
|
|||
}
|
||||
|
||||
// We don't actually check the entire buffer, just from synLinesUp to synLinesDown
|
||||
totalStart := v.topline - synLinesUp
|
||||
totalEnd := v.topline + v.height + synLinesDown
|
||||
totalStart := v.Topline - synLinesUp
|
||||
totalEnd := v.Topline + v.height + synLinesDown
|
||||
if totalStart < 0 {
|
||||
totalStart = 0
|
||||
}
|
||||
if totalEnd > buf.numLines {
|
||||
totalEnd = buf.numLines
|
||||
if totalEnd > buf.NumLines {
|
||||
totalEnd = buf.NumLines
|
||||
}
|
||||
|
||||
str := strings.Join(buf.lines[totalStart:totalEnd], "\n")
|
||||
startNum := ToCharPos(0, totalStart, v.buf)
|
||||
str := strings.Join(buf.Lines[totalStart:totalEnd], "\n")
|
||||
startNum := ToCharPos(0, totalStart, v.Buf)
|
||||
|
||||
toplineNum := ToCharPos(0, v.topline, v.buf)
|
||||
toplineNum := ToCharPos(0, v.Topline, v.Buf)
|
||||
|
||||
for _, rule := range rules {
|
||||
if rule.startend {
|
||||
|
@ -422,7 +422,7 @@ func Match(v *View) SyntaxMatches {
|
|||
if i < toplineNum {
|
||||
continue
|
||||
}
|
||||
colNum, lineNum := FromCharPosStart(toplineNum, 0, v.topline, i, buf)
|
||||
colNum, lineNum := FromCharPosStart(toplineNum, 0, v.Topline, i, buf)
|
||||
if lineNum == -1 || colNum == -1 {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -5,10 +5,13 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/layeh/gopher-luar"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/tcell/encoding"
|
||||
)
|
||||
|
@ -41,6 +44,9 @@ var (
|
|||
|
||||
// Is the help screen open
|
||||
helpOpen = false
|
||||
|
||||
// L is the lua state
|
||||
L *lua.LState
|
||||
)
|
||||
|
||||
// LoadInput loads the file input for the editor
|
||||
|
@ -175,6 +181,9 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
L = lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
encoding.Register()
|
||||
tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
|
||||
|
||||
|
@ -207,6 +216,14 @@ func main() {
|
|||
messenger = new(Messenger)
|
||||
view := NewView(buf)
|
||||
|
||||
L.SetGlobal("OS", luar.New(L, runtime.GOOS))
|
||||
L.SetGlobal("view", luar.New(L, view))
|
||||
L.SetGlobal("messenger", luar.New(L, messenger))
|
||||
L.SetGlobal("GetOption", luar.New(L, GetOption))
|
||||
L.SetGlobal("AddOption", luar.New(L, AddOption))
|
||||
|
||||
LoadPlugins()
|
||||
|
||||
for {
|
||||
// Display everything
|
||||
Redraw(view)
|
||||
|
@ -245,7 +262,7 @@ func main() {
|
|||
case tcell.KeyCtrlG:
|
||||
if !helpOpen {
|
||||
helpBuffer := NewBuffer(helpTxt, "help.md")
|
||||
helpBuffer.name = "Help"
|
||||
helpBuffer.Name = "Help"
|
||||
helpOpen = true
|
||||
view.OpenBuffer(helpBuffer)
|
||||
} else {
|
||||
|
|
63
cmd/micro/plugin.go
Normal file
63
cmd/micro/plugin.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/yuin/gopher-lua"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var loadedPlugins []string
|
||||
|
||||
var preInstalledPlugins = []string{
|
||||
"go",
|
||||
"linter",
|
||||
}
|
||||
|
||||
// Call calls the lua function 'function'
|
||||
// If it does not exist nothing happens, if there is an error,
|
||||
// the error is returned
|
||||
func Call(function string) error {
|
||||
luaFunc := L.GetGlobal(function)
|
||||
if luaFunc.String() == "nil" {
|
||||
return nil
|
||||
}
|
||||
err := L.CallByParam(lua.P{
|
||||
Fn: luaFunc,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
|
||||
func LoadPlugins() {
|
||||
files, _ := ioutil.ReadDir(configDir + "/plugins")
|
||||
for _, plugin := range files {
|
||||
if plugin.IsDir() {
|
||||
pluginName := plugin.Name()
|
||||
files, _ := ioutil.ReadDir(configDir + "/plugins/" + pluginName)
|
||||
for _, f := range files {
|
||||
if f.Name() == pluginName+".lua" {
|
||||
if err := L.DoFile(configDir + "/plugins/" + pluginName + "/" + f.Name()); err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
loadedPlugins = append(loadedPlugins, pluginName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pluginName := range preInstalledPlugins {
|
||||
plugin := "runtime/plugins/" + pluginName + "/" + pluginName + ".lua"
|
||||
data, err := Asset(plugin)
|
||||
if err != nil {
|
||||
TermMessage("Error loading pre-installed plugin: " + pluginName)
|
||||
continue
|
||||
}
|
||||
if err := L.DoString(string(data)); err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
loadedPlugins = append(loadedPlugins, pluginName)
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -57,7 +57,7 @@ func HandleSearchEvent(event tcell.Event, v *View) {
|
|||
}
|
||||
|
||||
if messenger.response == "" {
|
||||
v.cursor.ResetSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
// We don't end the search though
|
||||
return
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ func Search(searchStr string, v *View, down bool) {
|
|||
}
|
||||
var str string
|
||||
var charPos int
|
||||
text := v.buf.String()
|
||||
text := v.Buf.String()
|
||||
if down {
|
||||
str = text[searchStart:]
|
||||
charPos = searchStart
|
||||
|
@ -94,7 +94,7 @@ func Search(searchStr string, v *View, down bool) {
|
|||
matches = r.FindAllStringIndex(text, -1)
|
||||
charPos = 0
|
||||
if matches == nil {
|
||||
v.cursor.ResetSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -111,9 +111,9 @@ func Search(searchStr string, v *View, down bool) {
|
|||
match = matches[0]
|
||||
}
|
||||
|
||||
v.cursor.curSelection[0] = charPos + match[0]
|
||||
v.cursor.curSelection[1] = charPos + match[1]
|
||||
v.cursor.x, v.cursor.y = FromCharPos(charPos+match[1]-1, v.buf)
|
||||
v.Cursor.curSelection[0] = charPos + match[0]
|
||||
v.Cursor.curSelection[1] = charPos + match[1]
|
||||
v.Cursor.x, v.Cursor.y = FromCharPos(charPos+match[1]-1, v.Buf)
|
||||
if v.Relocate() {
|
||||
v.matches = Match(v)
|
||||
}
|
||||
|
|
|
@ -55,6 +55,20 @@ func WriteSettings(filename string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// AddOption creates a new option. This is meant to be called by plugins to add options.
|
||||
func AddOption(name string, value interface{}) {
|
||||
settings[name] = value
|
||||
err := WriteSettings(configDir + "/settings.json")
|
||||
if err != nil {
|
||||
TermMessage("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// GetOption returns the specified option. This is meant to be called by plugins to add options.
|
||||
func GetOption(name string) interface{} {
|
||||
return settings[name]
|
||||
}
|
||||
|
||||
// DefaultSettings returns the default settings for micro
|
||||
func DefaultSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
|
@ -64,8 +78,6 @@ func DefaultSettings() map[string]interface{} {
|
|||
"syntax": true,
|
||||
"tabsToSpaces": false,
|
||||
"ruler": true,
|
||||
"gofmt": false,
|
||||
"goimports": false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +114,7 @@ func SetOption(view *View, args []string) {
|
|||
|
||||
if option == "colorscheme" {
|
||||
LoadSyntaxFiles()
|
||||
view.buf.UpdateRules()
|
||||
view.Buf.UpdateRules()
|
||||
}
|
||||
|
||||
err := WriteSettings(filename)
|
||||
|
|
|
@ -17,14 +17,14 @@ func (sline *Statusline) Display() {
|
|||
// We'll draw the line at the lowest line in the view
|
||||
y := sline.view.height
|
||||
|
||||
file := sline.view.buf.name
|
||||
file := sline.view.Buf.Name
|
||||
// If the name is empty, use 'No name'
|
||||
if file == "" {
|
||||
file = "No name"
|
||||
}
|
||||
|
||||
// If the buffer is dirty (has been modified) write a little '+'
|
||||
if sline.view.buf.IsDirty() {
|
||||
if sline.view.Buf.IsDirty() {
|
||||
file += " +"
|
||||
}
|
||||
|
||||
|
@ -32,13 +32,13 @@ func (sline *Statusline) Display() {
|
|||
// but users will be used to (1,1) (first line,first column)
|
||||
// We use GetVisualX() here because otherwise we get the column number in runes
|
||||
// so a '\t' is only 1, when it should be tabSize
|
||||
columnNum := strconv.Itoa(sline.view.cursor.GetVisualX() + 1)
|
||||
lineNum := strconv.Itoa(sline.view.cursor.y + 1)
|
||||
columnNum := strconv.Itoa(sline.view.Cursor.GetVisualX() + 1)
|
||||
lineNum := strconv.Itoa(sline.view.Cursor.y + 1)
|
||||
|
||||
file += " (" + lineNum + "," + columnNum + ")"
|
||||
|
||||
// Add the filetype
|
||||
file += " " + sline.view.buf.filetype
|
||||
file += " " + sline.view.Buf.Filetype
|
||||
|
||||
rightText := "Ctrl-g for help "
|
||||
if helpOpen {
|
||||
|
|
|
@ -2,6 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -13,10 +15,10 @@ import (
|
|||
// It stores information about the cursor, and the viewport
|
||||
// that the user sees the buffer from.
|
||||
type View struct {
|
||||
cursor Cursor
|
||||
Cursor Cursor
|
||||
|
||||
// The topmost line, used for vertical scrolling
|
||||
topline int
|
||||
Topline int
|
||||
// The leftmost column, used for horizontal scrolling
|
||||
leftCol int
|
||||
|
||||
|
@ -34,10 +36,11 @@ type View struct {
|
|||
// The eventhandler for undo/redo
|
||||
eh *EventHandler
|
||||
|
||||
messages []GutterMessage
|
||||
// Holds the list of gutter messages
|
||||
messages map[string][]GutterMessage
|
||||
|
||||
// The buffer
|
||||
buf *Buffer
|
||||
Buf *Buffer
|
||||
// The statusline
|
||||
sline Statusline
|
||||
|
||||
|
@ -89,6 +92,8 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
|
|||
|
||||
v.eh = NewEventHandler(v)
|
||||
|
||||
v.messages = make(map[string][]GutterMessage)
|
||||
|
||||
v.sline = Statusline{
|
||||
view: v,
|
||||
}
|
||||
|
@ -111,20 +116,20 @@ func (v *View) Resize(w, h int) {
|
|||
// ScrollUp scrolls the view up n lines (if possible)
|
||||
func (v *View) ScrollUp(n int) {
|
||||
// Try to scroll by n but if it would overflow, scroll by 1
|
||||
if v.topline-n >= 0 {
|
||||
v.topline -= n
|
||||
} else if v.topline > 0 {
|
||||
v.topline--
|
||||
if v.Topline-n >= 0 {
|
||||
v.Topline -= n
|
||||
} else if v.Topline > 0 {
|
||||
v.Topline--
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollDown scrolls the view down n lines (if possible)
|
||||
func (v *View) ScrollDown(n int) {
|
||||
// Try to scroll by n but if it would overflow, scroll by 1
|
||||
if v.topline+n <= v.buf.numLines-v.height {
|
||||
v.topline += n
|
||||
} else if v.topline < v.buf.numLines-v.height {
|
||||
v.topline++
|
||||
if v.Topline+n <= v.Buf.NumLines-v.height {
|
||||
v.Topline += n
|
||||
} else if v.Topline < v.Buf.NumLines-v.height {
|
||||
v.Topline++
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,7 +138,7 @@ func (v *View) ScrollDown(n int) {
|
|||
// causing them to lose the unsaved changes
|
||||
// The message is what to print after saying "You have unsaved changes. "
|
||||
func (v *View) CanClose(msg string) bool {
|
||||
if v.buf.IsDirty() {
|
||||
if v.Buf.IsDirty() {
|
||||
quit, canceled := messenger.Prompt("You have unsaved changes. " + msg)
|
||||
if !canceled {
|
||||
if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" {
|
||||
|
@ -152,16 +157,17 @@ func (v *View) CanClose(msg string) bool {
|
|||
// OpenBuffer opens a new buffer in this view.
|
||||
// This resets the topline, event handler and cursor.
|
||||
func (v *View) OpenBuffer(buf *Buffer) {
|
||||
v.buf = buf
|
||||
v.topline = 0
|
||||
v.Buf = buf
|
||||
v.Topline = 0
|
||||
v.leftCol = 0
|
||||
// Put the cursor at the first spot
|
||||
v.cursor = Cursor{
|
||||
v.Cursor = Cursor{
|
||||
x: 0,
|
||||
y: 0,
|
||||
v: v,
|
||||
}
|
||||
v.cursor.ResetSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
v.messages = make(map[string][]GutterMessage)
|
||||
|
||||
v.eh = NewEventHandler(v)
|
||||
v.matches = Match(v)
|
||||
|
@ -172,20 +178,20 @@ func (v *View) OpenBuffer(buf *Buffer) {
|
|||
v.lastClickTime = time.Time{}
|
||||
}
|
||||
|
||||
// Close and Re-open the current file.
|
||||
func (v *View) reOpen() {
|
||||
// ReOpen reloads the current buffer
|
||||
func (v *View) ReOpen() {
|
||||
if v.CanClose("Continue? (yes, no, save) ") {
|
||||
file, err := ioutil.ReadFile(v.buf.path)
|
||||
filename := v.buf.name
|
||||
file, err := ioutil.ReadFile(v.Buf.Path)
|
||||
filename := v.Buf.Name
|
||||
|
||||
if err != nil {
|
||||
messenger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
buf := NewBuffer(string(file), filename)
|
||||
v.buf = buf
|
||||
v.Buf = buf
|
||||
v.matches = Match(v)
|
||||
v.cursor.Relocate()
|
||||
v.Cursor.Relocate()
|
||||
v.Relocate()
|
||||
}
|
||||
}
|
||||
|
@ -194,17 +200,17 @@ func (v *View) reOpen() {
|
|||
// This is useful if the user has scrolled far away, and then starts typing
|
||||
func (v *View) Relocate() bool {
|
||||
ret := false
|
||||
cy := v.cursor.y
|
||||
if cy < v.topline {
|
||||
v.topline = cy
|
||||
cy := v.Cursor.y
|
||||
if cy < v.Topline {
|
||||
v.Topline = cy
|
||||
ret = true
|
||||
}
|
||||
if cy > v.topline+v.height-1 {
|
||||
v.topline = cy - v.height + 1
|
||||
if cy > v.Topline+v.height-1 {
|
||||
v.Topline = cy - v.height + 1
|
||||
ret = true
|
||||
}
|
||||
|
||||
cx := v.cursor.GetVisualX()
|
||||
cx := v.Cursor.GetVisualX()
|
||||
if cx < v.leftCol {
|
||||
v.leftCol = cx
|
||||
ret = true
|
||||
|
@ -219,12 +225,12 @@ func (v *View) Relocate() bool {
|
|||
// MoveToMouseClick moves the cursor to location x, y assuming x, y were given
|
||||
// by a mouse click
|
||||
func (v *View) MoveToMouseClick(x, y int) {
|
||||
if y-v.topline > v.height-1 {
|
||||
if y-v.Topline > v.height-1 {
|
||||
v.ScrollDown(1)
|
||||
y = v.height + v.topline - 1
|
||||
y = v.height + v.Topline - 1
|
||||
}
|
||||
if y >= v.buf.numLines {
|
||||
y = v.buf.numLines - 1
|
||||
if y >= v.Buf.NumLines {
|
||||
y = v.Buf.NumLines - 1
|
||||
}
|
||||
if y < 0 {
|
||||
y = 0
|
||||
|
@ -233,13 +239,13 @@ func (v *View) MoveToMouseClick(x, y int) {
|
|||
x = 0
|
||||
}
|
||||
|
||||
x = v.cursor.GetCharPosInLine(y, x)
|
||||
if x > Count(v.buf.lines[y]) {
|
||||
x = Count(v.buf.lines[y])
|
||||
x = v.Cursor.GetCharPosInLine(y, x)
|
||||
if x > Count(v.Buf.Lines[y]) {
|
||||
x = Count(v.Buf.Lines[y])
|
||||
}
|
||||
v.cursor.x = x
|
||||
v.cursor.y = y
|
||||
v.cursor.lastVisualX = v.cursor.GetVisualX()
|
||||
v.Cursor.x = x
|
||||
v.Cursor.y = y
|
||||
v.Cursor.lastVisualX = v.Cursor.GetVisualX()
|
||||
}
|
||||
|
||||
// HandleEvent handles an event passed by the main loop
|
||||
|
@ -255,32 +261,39 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||
case *tcell.EventKey:
|
||||
if e.Key() == tcell.KeyRune {
|
||||
// Insert a character
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.eh.Insert(v.cursor.Loc(), string(e.Rune()))
|
||||
v.cursor.Right()
|
||||
v.eh.Insert(v.Cursor.Loc(), string(e.Rune()))
|
||||
v.Cursor.Right()
|
||||
} else {
|
||||
for key, action := range bindings {
|
||||
if e.Key() == key {
|
||||
relocate = action(v)
|
||||
for _, pl := range loadedPlugins {
|
||||
funcName := strings.Split(runtime.FuncForPC(reflect.ValueOf(action).Pointer()).Name(), ".")
|
||||
err := Call(pl + "_on" + funcName[len(funcName)-1])
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case *tcell.EventPaste:
|
||||
if v.cursor.HasSelection() {
|
||||
v.cursor.DeleteSelection()
|
||||
v.cursor.ResetSelection()
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
clip := e.Text()
|
||||
v.eh.Insert(v.cursor.Loc(), clip)
|
||||
v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
|
||||
v.eh.Insert(v.Cursor.Loc(), clip)
|
||||
v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
|
||||
v.freshClip = false
|
||||
case *tcell.EventMouse:
|
||||
x, y := e.Position()
|
||||
x -= v.lineNumOffset - v.leftCol
|
||||
y += v.topline
|
||||
y += v.Topline
|
||||
|
||||
button := e.Buttons()
|
||||
|
||||
|
@ -297,7 +310,7 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||
v.tripleClick = true
|
||||
v.doubleClick = false
|
||||
|
||||
v.cursor.SelectLine()
|
||||
v.Cursor.SelectLine()
|
||||
} else {
|
||||
// Double click
|
||||
v.lastClickTime = time.Now()
|
||||
|
@ -305,27 +318,27 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||
v.doubleClick = true
|
||||
v.tripleClick = false
|
||||
|
||||
v.cursor.SelectWord()
|
||||
v.Cursor.SelectWord()
|
||||
}
|
||||
} else {
|
||||
v.doubleClick = false
|
||||
v.tripleClick = false
|
||||
v.lastClickTime = time.Now()
|
||||
|
||||
loc := v.cursor.Loc()
|
||||
v.cursor.origSelection[0] = loc
|
||||
v.cursor.curSelection[0] = loc
|
||||
v.cursor.curSelection[1] = loc
|
||||
loc := v.Cursor.Loc()
|
||||
v.Cursor.origSelection[0] = loc
|
||||
v.Cursor.curSelection[0] = loc
|
||||
v.Cursor.curSelection[1] = loc
|
||||
}
|
||||
v.mouseReleased = false
|
||||
} else if !v.mouseReleased {
|
||||
v.MoveToMouseClick(x, y)
|
||||
if v.tripleClick {
|
||||
v.cursor.AddLineToSelection()
|
||||
v.Cursor.AddLineToSelection()
|
||||
} else if v.doubleClick {
|
||||
v.cursor.AddWordToSelection()
|
||||
v.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
v.cursor.curSelection[1] = v.cursor.Loc()
|
||||
v.Cursor.curSelection[1] = v.Cursor.Loc()
|
||||
}
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
|
@ -341,7 +354,7 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||
|
||||
if !v.doubleClick && !v.tripleClick {
|
||||
v.MoveToMouseClick(x, y)
|
||||
v.cursor.curSelection[1] = v.cursor.Loc()
|
||||
v.Cursor.curSelection[1] = v.Cursor.Loc()
|
||||
}
|
||||
v.mouseReleased = true
|
||||
}
|
||||
|
@ -370,28 +383,44 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||
}
|
||||
|
||||
// GutterMessage creates a message in this view's gutter
|
||||
func (v *View) GutterMessage(lineN int, msg string, kind int) {
|
||||
func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
|
||||
lineN--
|
||||
gutterMsg := GutterMessage{
|
||||
lineNum: lineN,
|
||||
msg: msg,
|
||||
kind: kind,
|
||||
}
|
||||
for _, gmsg := range v.messages {
|
||||
for _, v := range v.messages {
|
||||
for _, gmsg := range v {
|
||||
if gmsg.lineNum == lineN {
|
||||
return
|
||||
}
|
||||
}
|
||||
v.messages = append(v.messages, gutterMsg)
|
||||
}
|
||||
messages := v.messages[section]
|
||||
v.messages[section] = append(messages, gutterMsg)
|
||||
}
|
||||
|
||||
// ClearGutterMessages clears all gutter messages from a given section
|
||||
func (v *View) ClearGutterMessages(section string) {
|
||||
v.messages[section] = []GutterMessage{}
|
||||
}
|
||||
|
||||
// ClearAllGutterMessages clears all the gutter messages
|
||||
func (v *View) ClearAllGutterMessages() {
|
||||
for k := range v.messages {
|
||||
v.messages[k] = []GutterMessage{}
|
||||
}
|
||||
}
|
||||
|
||||
// DisplayView renders the view to the screen
|
||||
func (v *View) DisplayView() {
|
||||
// The character number of the character in the top left of the screen
|
||||
charNum := ToCharPos(0, v.topline, v.buf)
|
||||
charNum := ToCharPos(0, v.Topline, v.Buf)
|
||||
|
||||
// Convert the length of buffer to a string, and get the length of the string
|
||||
// We are going to have to offset by that amount
|
||||
maxLineLength := len(strconv.Itoa(v.buf.numLines))
|
||||
maxLineLength := len(strconv.Itoa(v.Buf.NumLines))
|
||||
// + 1 for the little space after the line number
|
||||
if settings["ruler"] == true {
|
||||
v.lineNumOffset = maxLineLength + 1
|
||||
|
@ -400,7 +429,13 @@ func (v *View) DisplayView() {
|
|||
}
|
||||
var highlightStyle tcell.Style
|
||||
|
||||
if len(v.messages) > 0 {
|
||||
var hasGutterMessages bool
|
||||
for _, v := range v.messages {
|
||||
if len(v) > 0 {
|
||||
hasGutterMessages = true
|
||||
}
|
||||
}
|
||||
if hasGutterMessages {
|
||||
v.lineNumOffset += 2
|
||||
}
|
||||
|
||||
|
@ -408,15 +443,16 @@ func (v *View) DisplayView() {
|
|||
var x int
|
||||
// If the buffer is smaller than the view height
|
||||
// and we went too far, break
|
||||
if lineN+v.topline >= v.buf.numLines {
|
||||
if lineN+v.Topline >= v.Buf.NumLines {
|
||||
break
|
||||
}
|
||||
line := v.buf.lines[lineN+v.topline]
|
||||
line := v.Buf.Lines[lineN+v.Topline]
|
||||
|
||||
if len(v.messages) > 0 {
|
||||
if hasGutterMessages {
|
||||
msgOnLine := false
|
||||
for _, msg := range v.messages {
|
||||
if msg.lineNum == lineN+v.topline {
|
||||
for k := range v.messages {
|
||||
for _, msg := range v.messages[k] {
|
||||
if msg.lineNum == lineN+v.Topline {
|
||||
msgOnLine = true
|
||||
gutterStyle := tcell.StyleDefault
|
||||
switch msg.kind {
|
||||
|
@ -437,18 +473,19 @@ func (v *View) DisplayView() {
|
|||
x++
|
||||
screen.SetContent(x, lineN, '>', nil, gutterStyle)
|
||||
x++
|
||||
if v.cursor.y == lineN {
|
||||
if v.Cursor.y == lineN+v.Topline {
|
||||
messenger.Message(msg.msg)
|
||||
messenger.gutterMessage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !msgOnLine {
|
||||
screen.SetContent(x, lineN, ' ', nil, tcell.StyleDefault)
|
||||
x++
|
||||
screen.SetContent(x, lineN, ' ', nil, tcell.StyleDefault)
|
||||
x++
|
||||
if v.cursor.y == lineN && messenger.gutterMessage {
|
||||
if v.Cursor.y == lineN+v.Topline && messenger.gutterMessage {
|
||||
messenger.Reset()
|
||||
messenger.gutterMessage = false
|
||||
}
|
||||
|
@ -463,7 +500,7 @@ func (v *View) DisplayView() {
|
|||
// Write the spaces before the line number if necessary
|
||||
var lineNum string
|
||||
if settings["ruler"] == true {
|
||||
lineNum = strconv.Itoa(lineN + v.topline + 1)
|
||||
lineNum = strconv.Itoa(lineN + v.Topline + 1)
|
||||
for i := 0; i < maxLineLength-len(lineNum); i++ {
|
||||
screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
|
||||
x++
|
||||
|
@ -490,9 +527,9 @@ func (v *View) DisplayView() {
|
|||
highlightStyle = v.matches[lineN][colN]
|
||||
}
|
||||
|
||||
if v.cursor.HasSelection() &&
|
||||
(charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] ||
|
||||
charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) {
|
||||
if v.Cursor.HasSelection() &&
|
||||
(charNum >= v.Cursor.curSelection[0] && charNum < v.Cursor.curSelection[1] ||
|
||||
charNum < v.Cursor.curSelection[0] && charNum >= v.Cursor.curSelection[1]) {
|
||||
|
||||
lineStyle = tcell.StyleDefault.Reverse(true)
|
||||
|
||||
|
@ -524,9 +561,9 @@ func (v *View) DisplayView() {
|
|||
|
||||
// The newline may be selected, in which case we should draw the selection style
|
||||
// with a space to represent it
|
||||
if v.cursor.HasSelection() &&
|
||||
(charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] ||
|
||||
charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) {
|
||||
if v.Cursor.HasSelection() &&
|
||||
(charNum >= v.Cursor.curSelection[0] && charNum < v.Cursor.curSelection[1] ||
|
||||
charNum < v.Cursor.curSelection[0] && charNum >= v.Cursor.curSelection[1]) {
|
||||
|
||||
selectStyle := defStyle.Reverse(true)
|
||||
|
||||
|
@ -543,6 +580,6 @@ func (v *View) DisplayView() {
|
|||
// Display renders the view, the cursor, and statusline
|
||||
func (v *View) Display() {
|
||||
v.DisplayView()
|
||||
v.cursor.Display()
|
||||
v.Cursor.Display()
|
||||
v.sline.Display()
|
||||
}
|
||||
|
|
|
@ -195,20 +195,24 @@ Here are the options that you can set:
|
|||
|
||||
default value: `on`
|
||||
|
||||
* `gofmt`: Run `gofmt` whenever the file is saved (this only applies to `.go`
|
||||
files)
|
||||
---
|
||||
|
||||
Default plugin options:
|
||||
|
||||
* `linter`: lint languages on save (supported languages are C, D, Go, Java,
|
||||
Javascript, Lua). Provided by the `linter` plugin.
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `goimports`: Run goimports on save. Provided by the `go` plugin.
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `goimports`: run `goimports` whenever the file is saved (this only applies
|
||||
to `.go` files)
|
||||
* `gofmt`: Run gofmt on save. Provided by the `go` plugin.
|
||||
|
||||
default value: `off`
|
||||
default value: `on`
|
||||
|
||||
Any option you set in the editor will be saved to the file
|
||||
~/.config/micro/settings.json so, in effect, your configuration file will be
|
||||
created for you. If you'd like to take your configuration with you to another
|
||||
machine, simply copy the settings.json to the other machine.
|
||||
|
||||
In the future, the `gofmt` and `goimports` will be refactored using a plugin
|
||||
system. However, currently they make it easier to program micro in micro.
|
||||
|
|
39
runtime/plugins/go/go.lua
Normal file
39
runtime/plugins/go/go.lua
Normal file
|
@ -0,0 +1,39 @@
|
|||
if GetOption("goimports") == nil then
|
||||
AddOption("goimports", false)
|
||||
end
|
||||
if GetOption("gofmt") == nil then
|
||||
AddOption("gofmt", true)
|
||||
end
|
||||
|
||||
function go_onSave()
|
||||
if view.Buf.Filetype == "Go" then
|
||||
if GetOption("goimports") then
|
||||
go_goimports()
|
||||
elseif GetOption("gofmt") then
|
||||
go_gofmt()
|
||||
end
|
||||
|
||||
view:ReOpen()
|
||||
end
|
||||
end
|
||||
|
||||
function go_gofmt()
|
||||
local handle = io.popen("gofmt -w " .. view.Buf.Path)
|
||||
local result = handle:read("*a")
|
||||
handle:close()
|
||||
end
|
||||
|
||||
function go_goimports()
|
||||
local handle = io.popen("goimports -w " .. view.Buf.Path)
|
||||
local result = go_split(handle:read("*a"), ":")
|
||||
handle:close()
|
||||
end
|
||||
|
||||
function go_split(str, sep)
|
||||
local result = {}
|
||||
local regex = ("([^%s]+)"):format(sep)
|
||||
for each in str:gmatch(regex) do
|
||||
table.insert(result, each)
|
||||
end
|
||||
return result
|
||||
end
|
66
runtime/plugins/linter/linter.lua
Normal file
66
runtime/plugins/linter/linter.lua
Normal file
|
@ -0,0 +1,66 @@
|
|||
if GetOption("linter") == nil then
|
||||
AddOption("linter", true)
|
||||
end
|
||||
|
||||
function linter_onSave()
|
||||
if GetOption("linter") then
|
||||
local ft = view.Buf.Filetype
|
||||
local file = view.Buf.Path
|
||||
local devnull = "/dev/null"
|
||||
if OS == "windows" then
|
||||
devnull = "NUL"
|
||||
end
|
||||
if ft == "Go" then
|
||||
linter_lint("gobuild", "go build -o " .. devnull, "%f:%l: %m")
|
||||
linter_lint("golint", "golint " .. view.Buf.Path, "%f:%l:%d+: %m")
|
||||
elseif ft == "Lua" then
|
||||
linter_lint("luacheck", "luacheck --no-color " .. file, "%f:%l:%d+: %m")
|
||||
elseif ft == "Python" then
|
||||
linter_lint("pyflakes", "pyflakes " .. file, "%f:%l: %m")
|
||||
elseif ft == "C" then
|
||||
linter_lint("gcc", "gcc -fsyntax-only -Wall -Wextra " .. file, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "D" then
|
||||
linter_lint("dmd", "dmd -color=off -o- -w -wi -c " .. file, "%f%(%l%):.+: %m")
|
||||
elseif ft == "Java" then
|
||||
linter_lint("javac", "javac " .. file, "%f:%l: error: %m")
|
||||
elseif ft == "JavaScript" then
|
||||
linter_lint("jshint", "jshint " .. file, "%f: line %l,.+, %m")
|
||||
end
|
||||
else
|
||||
view:ClearAllGutterMessages()
|
||||
end
|
||||
end
|
||||
|
||||
function linter_lint(linter, cmd, errorformat)
|
||||
view:ClearGutterMessages(linter)
|
||||
|
||||
local handle = io.popen("(" .. cmd .. ")" .. " 2>&1")
|
||||
local lines = linter_split(handle:read("*a"), "\n")
|
||||
handle:close()
|
||||
|
||||
local regex = errorformat:gsub("%%f", "(.+)"):gsub("%%l", "(%d+)"):gsub("%%m", "(.+)")
|
||||
for _,line in ipairs(lines) do
|
||||
-- Trim whitespace
|
||||
line = line:match("^%s*(.+)%s*$")
|
||||
if string.find(line, regex) then
|
||||
local file, line, msg = string.match(line, regex)
|
||||
if linter_basename(view.Buf.Path) == linter_basename(file) then
|
||||
view:GutterMessage(linter, tonumber(line), msg, 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function linter_split(str, sep)
|
||||
local result = {}
|
||||
local regex = ("([^%s]+)"):format(sep)
|
||||
for each in str:gmatch(regex) do
|
||||
table.insert(result, each)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function linter_basename(file)
|
||||
local name = string.gsub(file, "(.*/)(.*)", "%2")
|
||||
return name
|
||||
end
|
Loading…
Reference in a new issue