Merge pull request #82 from zyedidia/plugins

Plugin System
This commit is contained in:
Zachary Yedidia 2016-05-06 11:56:24 -04:00
commit aeff0f8170
16 changed files with 664 additions and 429 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
View 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

View file

@ -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)
}

View file

@ -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)

View file

@ -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 {

View file

@ -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()
}

View file

@ -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
View 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

View 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